Android: Add selection dialog (drop down/combo box) (#13814)

- The handling of IGUIComboBox uses the new setAndSendSelected() method.
- getDialogState() is now getInputDialogState() and returns the state of the input dialog.
- getLastDialogType() is added and returns current/last shown dialog's type.
- getInputDialogState() now returns an enum instead of int.
- getAndroidUIInput() now returns void instead of bool.
- New data types (enum) are added:
  (1) GameActivity.DialogType (Java) and porting::AndroidDialogType (C++)
  (2) GameActivity.DialogState (Java) and porting::AndroidDialogState (C++)
- When showing a text input dialog, there is no custom accept button text any more.
- showDialog()/showDialogUI() for text input is now showTextInputDialog()/showTextInputDialogUI().
- showInputDialog()/showDialogUI() for text input is now showTextInputDialog()/showTextInputDialogUI().
- getDialogValue()/getInputDialogValue() is now getDialogMessage()/getInputDialogMessage().


Co-authored-by: Gregor Parzefall <82708541+grorp@users.noreply.github.com>
This commit is contained in:
Muhammad Rifqi Priyo Susanto 2024-01-07 19:00:04 +07:00 committed by GitHub
parent bd42cc2c77
commit 171f911237
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 259 additions and 106 deletions

@ -51,8 +51,13 @@ public class GameActivity extends NativeActivity {
System.loadLibrary("minetest"); System.loadLibrary("minetest");
} }
private int messageReturnCode = -1; enum DialogType { TEXT_INPUT, SELECTION_INPUT }
enum DialogState { DIALOG_SHOWN, DIALOG_INPUTTED, DIALOG_CANCELED }
private DialogType lastDialogType = DialogType.TEXT_INPUT;
private DialogState inputDialogState = DialogState.DIALOG_CANCELED;
private String messageReturnValue = ""; private String messageReturnValue = "";
private int selectionReturnValue = 0;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -85,11 +90,17 @@ public class GameActivity extends NativeActivity {
// Ignore the back press so Minetest can handle it // Ignore the back press so Minetest can handle it
} }
public void showDialog(String acceptButton, String hint, String current, int editType) { public void showTextInputDialog(String hint, String current, int editType) {
runOnUiThread(() -> showDialogUI(hint, current, editType)); runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
} }
private void showDialogUI(String hint, String current, int editType) { public void showSelectionInputDialog(String[] optionList, int selectedIdx) {
runOnUiThread(() -> showSelectionInputDialogUI(optionList, selectedIdx));
}
private void showTextInputDialogUI(String hint, String current, int editType) {
lastDialogType = DialogType.TEXT_INPUT;
inputDialogState = DialogState.DIALOG_SHOWN;
final AlertDialog.Builder builder = new AlertDialog.Builder(this); final AlertDialog.Builder builder = new AlertDialog.Builder(this);
LinearLayout container = new LinearLayout(this); LinearLayout container = new LinearLayout(this);
container.setOrientation(LinearLayout.VERTICAL); container.setOrientation(LinearLayout.VERTICAL);
@ -114,7 +125,7 @@ public class GameActivity extends NativeActivity {
// For multi-line, do not submit the text after pressing Enter key // For multi-line, do not submit the text after pressing Enter key
if (keyCode == KeyEvent.KEYCODE_ENTER && editType != 1) { if (keyCode == KeyEvent.KEYCODE_ENTER && editType != 1) {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
messageReturnCode = 0; inputDialogState = DialogState.DIALOG_INPUTTED;
messageReturnValue = editText.getText().toString(); messageReturnValue = editText.getText().toString();
alertDialog.dismiss(); alertDialog.dismiss();
return true; return true;
@ -128,29 +139,55 @@ public class GameActivity extends NativeActivity {
doneButton.setText(R.string.ime_dialog_done); doneButton.setText(R.string.ime_dialog_done);
doneButton.setOnClickListener((view -> { doneButton.setOnClickListener((view -> {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
messageReturnCode = 0; inputDialogState = DialogState.DIALOG_INPUTTED;
messageReturnValue = editText.getText().toString(); messageReturnValue = editText.getText().toString();
alertDialog.dismiss(); alertDialog.dismiss();
})); }));
} }
alertDialog.setOnCancelListener(dialog -> { alertDialog.setOnCancelListener(dialog -> {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
inputDialogState = DialogState.DIALOG_CANCELED;
messageReturnValue = current; messageReturnValue = current;
messageReturnCode = -1;
}); });
alertDialog.show(); alertDialog.show();
editText.requestFocusTryShow(); editText.requestFocusTryShow();
} }
public int getDialogState() { public void showSelectionInputDialogUI(String[] optionList, int selectedIdx) {
return messageReturnCode; lastDialogType = DialogType.SELECTION_INPUT;
inputDialogState = DialogState.DIALOG_SHOWN;
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setSingleChoiceItems(optionList, selectedIdx, (dialog, selection) -> {
inputDialogState = DialogState.DIALOG_INPUTTED;
selectionReturnValue = selection;
dialog.dismiss();
});
builder.setOnCancelListener(dialog -> {
inputDialogState = DialogState.DIALOG_CANCELED;
selectionReturnValue = selectedIdx;
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
} }
public String getDialogValue() { public int getLastDialogType() {
messageReturnCode = -1; return lastDialogType.ordinal();
}
public int getInputDialogState() {
return inputDialogState.ordinal();
}
public String getDialogMessage() {
inputDialogState = DialogState.DIALOG_CANCELED;
return messageReturnValue; return messageReturnValue;
} }
public int getDialogSelection() {
inputDialogState = DialogState.DIALOG_CANCELED;
return selectionReturnValue;
}
public float getDensity() { public float getDensity() {
return getResources().getDisplayMetrics().density; return getResources().getDisplayMetrics().density;
} }

@ -2277,7 +2277,7 @@ void Game::openConsole(float scale, const wchar_t *line)
assert(scale > 0.0f && scale <= 1.0f); assert(scale > 0.0f && scale <= 1.0f);
#ifdef __ANDROID__ #ifdef __ANDROID__
porting::showInputDialog(gettext("ok"), "", "", 2); porting::showTextInputDialog("", "", 2);
m_android_chat_open = true; m_android_chat_open = true;
#else #else
if (gui_chat_console->isOpenInhibited()) if (gui_chat_console->isOpenInhibited())
@ -2293,15 +2293,19 @@ void Game::openConsole(float scale, const wchar_t *line)
#ifdef __ANDROID__ #ifdef __ANDROID__
void Game::handleAndroidChatInput() void Game::handleAndroidChatInput()
{ {
if (m_android_chat_open && porting::getInputDialogState() == 0) { // It has to be a text input
std::string text = porting::getInputDialogValue(); if (m_android_chat_open && porting::getLastInputDialogType() == porting::TEXT_INPUT) {
porting::AndroidDialogState dialogState = porting::getInputDialogState();
if (dialogState == porting::DIALOG_INPUTTED) {
std::string text = porting::getInputDialogMessage();
client->typeChatMessage(utf8_to_wide(text)); client->typeChatMessage(utf8_to_wide(text));
}
if (dialogState != porting::DIALOG_SHOWN)
m_android_chat_open = false; m_android_chat_open = false;
} }
} }
#endif #endif
void Game::toggleFreeMove() void Game::toggleFreeMove()
{ {
bool free_move = !g_settings->getBool("free_move"); bool free_move = !g_settings->getBool("free_move");

@ -3497,29 +3497,34 @@ void GUIFormSpecMenu::legacySortElements(std::list<IGUIElement *>::iterator from
} }
#ifdef __ANDROID__ #ifdef __ANDROID__
bool GUIFormSpecMenu::getAndroidUIInput() void GUIFormSpecMenu::getAndroidUIInput()
{ {
if (!hasAndroidUIInput()) porting::AndroidDialogState dialogState = getAndroidUIInputState();
return false; if (dialogState == porting::DIALOG_SHOWN) {
return;
} else if (dialogState == porting::DIALOG_CANCELED) {
m_jni_field_name.clear();
return;
}
// still waiting porting::AndroidDialogType dialog_type = porting::getLastInputDialogType();
if (porting::getInputDialogState() == -1)
return true;
std::string fieldname = m_jni_field_name; std::string fieldname = m_jni_field_name;
m_jni_field_name.clear(); m_jni_field_name.clear();
for (const FieldSpec &field : m_fields) { for (const FieldSpec &field : m_fields) {
if (field.fname != fieldname) if (field.fname != fieldname)
continue; continue; // Iterate until found
IGUIElement *element = getElementFromId(field.fid, true); IGUIElement *element = getElementFromId(field.fid, true);
if (!element || element->getType() != irr::gui::EGUIET_EDIT_BOX) if (!element)
return false; return;
auto element_type = element->getType();
if (dialog_type == porting::TEXT_INPUT && element_type == irr::gui::EGUIET_EDIT_BOX) {
gui::IGUIEditBox *editbox = (gui::IGUIEditBox *)element; gui::IGUIEditBox *editbox = (gui::IGUIEditBox *)element;
std::string text = porting::getInputDialogValue(); std::string text = porting::getInputDialogMessage();
editbox->setText(utf8_to_wide(text).c_str()); editbox->setText(utf8_to_wide(text).c_str());
bool enter_after_edit = false; bool enter_after_edit = false;
@ -3535,8 +3540,15 @@ bool GUIFormSpecMenu::getAndroidUIInput()
enter.GUIEvent.EventType = gui::EGET_EDITBOX_ENTER; enter.GUIEvent.EventType = gui::EGET_EDITBOX_ENTER;
editbox->getParent()->OnEvent(enter); editbox->getParent()->OnEvent(enter);
} }
} else if (dialog_type == porting::SELECTION_INPUT &&
element_type == irr::gui::EGUIET_COMBO_BOX) {
auto dropdown = (gui::IGUIComboBox *) element;
int selected = porting::getInputDialogSelection();
dropdown->setAndSendSelected(selected);
}
return; // Early-return after found
} }
return false;
} }
#endif #endif

@ -286,7 +286,7 @@ public:
core::rect<s32> getAbsoluteRect(); core::rect<s32> getAbsoluteRect();
#ifdef __ANDROID__ #ifdef __ANDROID__
bool getAndroidUIInput(); void getAndroidUIInput();
#endif #endif
protected: protected:

@ -264,14 +264,19 @@ std::string GUIPasswordChange::getNameByID(s32 id)
} }
#ifdef __ANDROID__ #ifdef __ANDROID__
bool GUIPasswordChange::getAndroidUIInput() void GUIPasswordChange::getAndroidUIInput()
{ {
if (!hasAndroidUIInput()) porting::AndroidDialogState dialogState = getAndroidUIInputState();
return false; if (dialogState == porting::DIALOG_SHOWN) {
return;
} else if (dialogState == porting::DIALOG_CANCELED) {
m_jni_field_name.clear();
return;
}
// still waiting // It has to be a text input
if (porting::getInputDialogState() == -1) if (porting::getLastInputDialogType() != porting::TEXT_INPUT)
return true; return;
gui::IGUIElement *e = nullptr; gui::IGUIElement *e = nullptr;
if (m_jni_field_name == "old_password") if (m_jni_field_name == "old_password")
@ -283,10 +288,10 @@ bool GUIPasswordChange::getAndroidUIInput()
m_jni_field_name.clear(); m_jni_field_name.clear();
if (!e || e->getType() != irr::gui::EGUIET_EDIT_BOX) if (!e || e->getType() != irr::gui::EGUIET_EDIT_BOX)
return false; return;
std::string text = porting::getInputDialogValue(); std::string text = porting::getInputDialogMessage();
e->setText(utf8_to_wide(text).c_str()); e->setText(utf8_to_wide(text).c_str());
return false; return;
} }
#endif #endif

@ -45,7 +45,7 @@ public:
bool OnEvent(const SEvent &event); bool OnEvent(const SEvent &event);
#ifdef __ANDROID__ #ifdef __ANDROID__
bool getAndroidUIInput(); void getAndroidUIInput();
#endif #endif
protected: protected:

@ -268,11 +268,34 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
if (((gui::IGUIEditBox *)hovered)->isPasswordBox()) if (((gui::IGUIEditBox *)hovered)->isPasswordBox())
type = 3; type = 3;
porting::showInputDialog(gettext("OK"), "", porting::showTextInputDialog("",
wide_to_utf8(((gui::IGUIEditBox *)hovered)->getText()), type); wide_to_utf8(((gui::IGUIEditBox *) hovered)->getText()), type);
return retval; return retval;
} }
} }
if (event.EventType == EET_GUI_EVENT) {
if (event.GUIEvent.EventType == gui::EGET_LISTBOX_OPENED) {
gui::IGUIComboBox *dropdown = (gui::IGUIComboBox *) event.GUIEvent.Caller;
std::string field_name = getNameByID(dropdown->getID());
if (field_name.empty())
return false;
m_jni_field_name = field_name;
s32 selected_idx = dropdown->getSelected();
s32 option_size = dropdown->getItemCount();
std::string list_of_options[option_size];
for (s32 i = 0; i < option_size; i++) {
list_of_options[i] = wide_to_utf8(dropdown->getItem(i));
}
porting::showComboBoxDialog(list_of_options, option_size, selected_idx);
return true; // Prevent the Irrlicht dropdown from opening.
}
}
#endif #endif
// Convert touch events into mouse events. // Convert touch events into mouse events.
@ -347,22 +370,12 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
} }
#ifdef __ANDROID__ #ifdef __ANDROID__
bool GUIModalMenu::hasAndroidUIInput() porting::AndroidDialogState GUIModalMenu::getAndroidUIInputState()
{ {
// no dialog shown // No dialog is shown
if (m_jni_field_name.empty()) if (m_jni_field_name.empty())
return false; return porting::DIALOG_CANCELED;
// still waiting return porting::getInputDialogState();
if (porting::getInputDialogState() == -1)
return true;
// no value abort dialog processing
if (porting::getInputDialogState() != 0) {
m_jni_field_name.clear();
return false;
}
return true;
} }
#endif #endif

@ -22,6 +22,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes_extrabloated.h" #include "irrlichttypes_extrabloated.h"
#include "irr_ptr.h" #include "irr_ptr.h"
#include "util/string.h" #include "util/string.h"
#ifdef __ANDROID__
#include <porting_android.h>
#endif
enum class PointerType { enum class PointerType {
Mouse, Mouse,
@ -59,8 +62,8 @@ public:
virtual bool OnEvent(const SEvent &event) { return false; }; virtual bool OnEvent(const SEvent &event) { return false; };
virtual bool pausesGame() { return false; } // Used for pause menu virtual bool pausesGame() { return false; } // Used for pause menu
#ifdef __ANDROID__ #ifdef __ANDROID__
virtual bool getAndroidUIInput() { return false; } virtual void getAndroidUIInput() {};
bool hasAndroidUIInput(); porting::AndroidDialogState getAndroidUIInputState();
#endif #endif
PointerType getPointerType() { return m_pointer_type; }; PointerType getPointerType() { return m_pointer_type; };

@ -165,22 +165,41 @@ bool setSystemPaths()
return true; return true;
} }
void showInputDialog(const std::string &acceptButton, const std::string &hint, void showTextInputDialog(const std::string &hint, const std::string &current, int editType)
const std::string &current, int editType)
{ {
jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showDialog", jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showTextInputDialog",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V"); "(Ljava/lang/String;Ljava/lang/String;I)V");
FATAL_ERROR_IF(showdialog == nullptr, FATAL_ERROR_IF(showdialog == nullptr,
"porting::showInputDialog unable to find Java showDialog method"); "porting::showTextInputDialog unable to find Java showTextInputDialog method");
jstring jacceptButton = jnienv->NewStringUTF(acceptButton.c_str());
jstring jhint = jnienv->NewStringUTF(hint.c_str()); jstring jhint = jnienv->NewStringUTF(hint.c_str());
jstring jcurrent = jnienv->NewStringUTF(current.c_str()); jstring jcurrent = jnienv->NewStringUTF(current.c_str());
jint jeditType = editType; jint jeditType = editType;
jnienv->CallVoidMethod(app_global->activity->clazz, showdialog, jnienv->CallVoidMethod(app_global->activity->clazz, showdialog,
jacceptButton, jhint, jcurrent, jeditType); jhint, jcurrent, jeditType);
}
void showComboBoxDialog(const std::string optionList[], s32 listSize, s32 selectedIdx)
{
jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showSelectionInputDialog",
"([Ljava/lang/String;I)V");
FATAL_ERROR_IF(showdialog == nullptr,
"porting::showComboBoxDialog unable to find Java showSelectionInputDialog method");
jclass jStringClass = jnienv->FindClass("java/lang/String");
jobjectArray jOptionList = jnienv->NewObjectArray(listSize, jStringClass, NULL);
jint jselectedIdx = selectedIdx;
for (s32 i = 0; i < listSize; i ++) {
jnienv->SetObjectArrayElement(jOptionList, i,
jnienv->NewStringUTF(optionList[i].c_str()));
}
jnienv->CallVoidMethod(app_global->activity->clazz, showdialog, jOptionList,
jselectedIdx);
} }
void openURIAndroid(const char *url) void openURIAndroid(const char *url)
@ -207,30 +226,53 @@ void shareFileAndroid(const std::string &path)
jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl); jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl);
} }
int getInputDialogState() AndroidDialogType getLastInputDialogType()
{ {
jmethodID dialogstate = jnienv->GetMethodID(nativeActivity, jmethodID lastdialogtype = jnienv->GetMethodID(nativeActivity,
"getDialogState", "()I"); "getLastDialogType", "()I");
FATAL_ERROR_IF(dialogstate == nullptr, FATAL_ERROR_IF(lastdialogtype == nullptr,
"porting::getInputDialogState unable to find Java getDialogState method"); "porting::getLastInputDialogType unable to find Java getLastDialogType method");
return jnienv->CallIntMethod(app_global->activity->clazz, dialogstate); int dialogType = jnienv->CallIntMethod(app_global->activity->clazz, lastdialogtype);
return static_cast<AndroidDialogType>(dialogType);
} }
std::string getInputDialogValue() AndroidDialogState getInputDialogState()
{
jmethodID inputdialogstate = jnienv->GetMethodID(nativeActivity,
"getInputDialogState", "()I");
FATAL_ERROR_IF(inputdialogstate == nullptr,
"porting::getInputDialogState unable to find Java getInputDialogState method");
int dialogState = jnienv->CallIntMethod(app_global->activity->clazz, inputdialogstate);
return static_cast<AndroidDialogState>(dialogState);
}
std::string getInputDialogMessage()
{ {
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity, jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity,
"getDialogValue", "()Ljava/lang/String;"); "getDialogMessage", "()Ljava/lang/String;");
FATAL_ERROR_IF(dialogvalue == nullptr, FATAL_ERROR_IF(dialogvalue == nullptr,
"porting::getInputDialogValue unable to find Java getDialogValue method"); "porting::getInputDialogMessage unable to find Java getDialogMessage method");
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, jobject result = jnienv->CallObjectMethod(app_global->activity->clazz,
dialogvalue); dialogvalue);
return readJavaString((jstring) result); return readJavaString((jstring) result);
} }
int getInputDialogSelection()
{
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity, "getDialogSelection", "()I");
FATAL_ERROR_IF(dialogvalue == nullptr,
"porting::getInputDialogSelection unable to find Java getDialogSelection method");
return jnienv->CallIntMethod(app_global->activity->clazz, dialogvalue);
}
#ifndef SERVER #ifndef SERVER
float getDisplayDensity() float getDisplayDensity()
{ {

@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once #pragma once
#ifndef __ANDROID__ #ifndef __ANDROID__
#error this include has to be included on android port only! #error This header has to be included on Android port only!
#endif #endif
#include <jni.h> #include <jni.h>
@ -30,22 +30,28 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string> #include <string>
namespace porting { namespace porting {
// java app // Java app
extern android_app *app_global; extern android_app *app_global;
// java <-> c++ interaction interface // Java <-> C++ interaction interface
extern JNIEnv *jnienv; extern JNIEnv *jnienv;
/** /**
* show text input dialog in java * Show a text input dialog in Java
* @param acceptButton text to display on accept button * @param hint Hint to be shown
* @param hint hint to show * @param current Initial value to be displayed
* @param current initial value to display * @param editType Type of the text field
* @param editType type of texfield * (1 = multi-line text input; 2 = single-line text input; 3 = password field)
* (1==multiline text input; 2==single line text input; 3=password field)
*/ */
void showInputDialog(const std::string &acceptButton, void showTextInputDialog(const std::string &hint, const std::string &current, int editType);
const std::string &hint, const std::string &current, int editType);
/**
* Show a selection dialog in Java
* @param optionList The list of options
* @param listSize Size of the list
* @param selectedIdx Selected index
*/
void showComboBoxDialog(const std::string optionList[], s32 listSize, s32 selectedIdx);
/** /**
* Opens a share intent to the file at path * Opens a share intent to the file at path
@ -54,17 +60,48 @@ void showInputDialog(const std::string &acceptButton,
*/ */
void shareFileAndroid(const std::string &path); void shareFileAndroid(const std::string &path);
/** /*
* WORKAROUND for not working callbacks from java -> c++ * Types of Android input dialog:
* get current state of input dialog * 1. Text input (single/multi-line text and password field)
* 2. Selection input (combo box)
*/ */
int getInputDialogState(); enum AndroidDialogType { TEXT_INPUT, SELECTION_INPUT };
/** /*
* WORKAROUND for not working callbacks from java -> c++ * WORKAROUND for not working callbacks from Java -> C++
* get text in current input dialog * Get the type of the last input dialog
*/ */
std::string getInputDialogValue(); AndroidDialogType getLastInputDialogType();
/*
* States of Android input dialog:
* 1. The dialog is currently shown.
* 2. The dialog has its input sent.
* 3. The dialog is canceled/dismissed.
*/
enum AndroidDialogState { DIALOG_SHOWN, DIALOG_INPUTTED, DIALOG_CANCELED };
/*
* WORKAROUND for not working callbacks from Java -> C++
* Get the state of the input dialog
*/
AndroidDialogState getInputDialogState();
/*
* WORKAROUND for not working callbacks from Java -> C++
* Get the text in the current/last input dialog
* This function clears the dialog state (set to canceled). Make sure to save
* the dialog state before calling this function.
*/
std::string getInputDialogMessage();
/*
* WORKAROUND for not working callbacks from Java -> C++
* Get the selection in the current/last input dialog
* This function clears the dialog state (set to canceled). Make sure to save
* the dialog state before calling this function.
*/
int getInputDialogSelection();
#ifndef SERVER #ifndef SERVER
float getDisplayDensity(); float getDisplayDensity();