From 2a0e6a5b03ed8545ab4848e41e4758b72e4ab35e Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:30:09 -0300 Subject: [PATCH 1/6] Add dialog_simple_edit_text layout for dialogs It has a title, a description for an input field, an input field, a cancel and a OK button --- .../res/layout/dialog_simple_edit_text.xml | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 app/src/main/res/layout/dialog_simple_edit_text.xml diff --git a/app/src/main/res/layout/dialog_simple_edit_text.xml b/app/src/main/res/layout/dialog_simple_edit_text.xml new file mode 100644 index 0000000..6de6019 --- /dev/null +++ b/app/src/main/res/layout/dialog_simple_edit_text.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + \ No newline at end of file From af65f33438b396d35e3088a423524e8c4981a43c Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:47:43 -0300 Subject: [PATCH 2/6] Use dialog_simple_edit_text in Add Profile --- .../client/ui/profile/ProfilesFragment.java | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java b/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java index 202f8b9..d5fceac 100644 --- a/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java +++ b/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java @@ -1,5 +1,6 @@ package io.netbird.client.ui.profile; +import android.annotation.SuppressLint; import android.app.AlertDialog; import android.os.Bundle; import android.util.Log; @@ -7,6 +8,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.EditText; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -15,6 +17,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.google.android.material.button.MaterialButton; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; @@ -26,11 +29,10 @@ public class ProfilesFragment extends Fragment { private static final String TAG = "ProfilesFragment"; - private RecyclerView recyclerView; private ProfilesAdapter adapter; private ProfileManagerWrapper profileManager; - private List profiles = new ArrayList<>(); + private final List profiles = new ArrayList<>(); @Nullable @Override @@ -76,23 +78,27 @@ private void loadProfiles() { adapter.notifyDataSetChanged(); } + @SuppressLint("InflateParams") private void showAddDialog() { - View dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_simple_alert_message, null); - final EditText input = new EditText(requireContext()); + View dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_simple_edit_text, null); + + TextView txtTitle = dialogView.findViewById(R.id.text_title_dialog); + TextView txtLabel = dialogView.findViewById(R.id.text_label_dialog); + EditText input = dialogView.findViewById(R.id.edit_text_dialog); + MaterialButton btnCancel = dialogView.findViewById(R.id.btn_cancel_dialog); + MaterialButton btnOk = dialogView.findViewById(R.id.btn_ok_dialog); + + txtTitle.setText(R.string.profiles_dialog_add_title); + txtLabel.setText(R.string.profiles_dialog_add_message); input.setHint(R.string.profiles_dialog_add_hint); - final AlertDialog dialog = new AlertDialog.Builder(requireContext()) - .setTitle(R.string.profiles_dialog_add_title) - .setMessage(R.string.profiles_dialog_add_message) - .setView(input) - .setPositiveButton(android.R.string.ok, null) - .setNegativeButton(android.R.string.cancel, null) + final AlertDialog dialog = new AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme) + .setView(dialogView) .create(); - dialog.show(); + btnCancel.setOnClickListener(v -> dialog.dismiss()); - // Set click listener after show() to prevent auto-dismiss on validation failure - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { + btnOk.setOnClickListener(v -> { String profileName = input.getText().toString().trim(); if (profileName.isEmpty()) { Toast.makeText(requireContext(), R.string.profiles_error_empty_name, Toast.LENGTH_SHORT).show(); @@ -111,6 +117,8 @@ private void showAddDialog() { addProfile(profileName); dialog.dismiss(); }); + + dialog.show(); } /** From ff7d72db50e2888b80f1de2eb79986275180780e Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:48:11 -0300 Subject: [PATCH 3/6] Adjust focus highlight corners to 2dp --- app/src/main/res/drawable/focus_highlight.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/drawable/focus_highlight.xml b/app/src/main/res/drawable/focus_highlight.xml index 2aa541a..2cb9f79 100644 --- a/app/src/main/res/drawable/focus_highlight.xml +++ b/app/src/main/res/drawable/focus_highlight.xml @@ -8,7 +8,7 @@ - + @@ -16,7 +16,7 @@ - + From 1fc61bdf1ebcbdc2b827b6511c128c18855f2820 Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:16:17 -0300 Subject: [PATCH 4/6] Add createDialog method to ProfilesFragment Centralizes layout inflation and dialog creation. Receives a title, a message and an optional hint to be displayed in the dialog's EditText view; if the hint is sent as null, the dialog won't display its EditText. DialogCallback interface defines an onConfirm method that receives a nullable input text to be used as the confirmation button's action; its return value indicates whether the dialog should be dismissed or not after its execution. --- .../client/ui/profile/ProfilesFragment.java | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java b/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java index d5fceac..d06c08c 100644 --- a/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java +++ b/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java @@ -1,5 +1,7 @@ package io.netbird.client.ui.profile; +import static android.view.View.GONE; + import android.annotation.SuppressLint; import android.app.AlertDialog; import android.os.Bundle; @@ -17,7 +19,6 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.button.MaterialButton; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; @@ -78,7 +79,45 @@ private void loadProfiles() { adapter.notifyDataSetChanged(); } + interface DialogCallback { + // Return true to dismiss dialog. + boolean onConfirm(@Nullable String inputText); + } + @SuppressLint("InflateParams") + private AlertDialog createDialog(String title, String message, @Nullable String inputHint, DialogCallback callback) { + boolean hasInput = inputHint != null; + + View dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_simple_edit_text, null); + + TextView txtTitle = dialogView.findViewById(R.id.text_title_dialog); + txtTitle.setText(title); + + TextView txtMessage = dialogView.findViewById(R.id.text_label_dialog); + txtMessage.setText(message); + + EditText input = dialogView.findViewById(R.id.edit_text_dialog); + if (hasInput) { + input.setHint(inputHint); + } else { + input.setVisibility(GONE); + } + + AlertDialog dialog = new AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme) + .setView(dialogView) + .create(); + + dialogView.findViewById(R.id.btn_cancel_dialog).setOnClickListener(v -> dialog.dismiss()); + dialogView.findViewById(R.id.btn_ok_dialog).setOnClickListener(v -> { + String inputText = hasInput ? input.getText().toString().trim() : null; + if (callback.onConfirm(inputText)) { + dialog.dismiss(); + } + }); + + return dialog; + } + private void showAddDialog() { View dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_simple_edit_text, null); From 15edb399369bb0732fb1f59191687c7f90ef4266 Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:17:31 -0300 Subject: [PATCH 5/6] Use createDialog in show dialog methods showAddDialog, showSwitchDialog, showLogoutDialog and showRemoveDialog were changed to make use of createDialog. --- .../client/ui/profile/ProfilesFragment.java | 109 ++++++++---------- 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java b/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java index d06c08c..9296657 100644 --- a/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java +++ b/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java @@ -119,45 +119,28 @@ private AlertDialog createDialog(String title, String message, @Nullable String } private void showAddDialog() { - View dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_simple_edit_text, null); - - TextView txtTitle = dialogView.findViewById(R.id.text_title_dialog); - TextView txtLabel = dialogView.findViewById(R.id.text_label_dialog); - EditText input = dialogView.findViewById(R.id.edit_text_dialog); - MaterialButton btnCancel = dialogView.findViewById(R.id.btn_cancel_dialog); - MaterialButton btnOk = dialogView.findViewById(R.id.btn_ok_dialog); - - txtTitle.setText(R.string.profiles_dialog_add_title); - txtLabel.setText(R.string.profiles_dialog_add_message); - input.setHint(R.string.profiles_dialog_add_hint); - - final AlertDialog dialog = new AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme) - .setView(dialogView) - .create(); - - btnCancel.setOnClickListener(v -> dialog.dismiss()); - - btnOk.setOnClickListener(v -> { - String profileName = input.getText().toString().trim(); - if (profileName.isEmpty()) { - Toast.makeText(requireContext(), R.string.profiles_error_empty_name, Toast.LENGTH_SHORT).show(); - return; - } - - // Validate profile name based on go client sanitization rules - String sanitizedName = sanitizeProfileName(profileName); - if (sanitizedName.isEmpty()) { - Toast.makeText(requireContext(), - "Profile name must contain at least one letter, digit, underscore or hyphen", - Toast.LENGTH_LONG).show(); - return; - } - - addProfile(profileName); - dialog.dismiss(); - }); - - dialog.show(); + createDialog( + getString(R.string.profiles_dialog_add_title), + getString(R.string.profiles_dialog_add_message), + getString(R.string.profiles_dialog_add_hint), + profileName -> { + if (profileName == null || profileName.isEmpty()) { + Toast.makeText(requireContext(), R.string.profiles_error_empty_name, Toast.LENGTH_SHORT).show(); + return false; + } + + // Validate profile name based on go client sanitization rules + String sanitizedName = sanitizeProfileName(profileName); + if (sanitizedName.isEmpty()) { + Toast.makeText(requireContext(), + "Profile name must contain at least one letter, digit, underscore or hyphen", + Toast.LENGTH_LONG).show(); + return false; + } + + addProfile(profileName); + return true; + }).show(); } /** @@ -177,33 +160,39 @@ private String sanitizeProfileName(String name) { } private void showSwitchDialog(Profile profile) { - String message = getString(R.string.profiles_dialog_switch_message, profile.getName()); - new AlertDialog.Builder(requireContext()) - .setTitle(R.string.profiles_dialog_switch_title) - .setMessage(message) - .setPositiveButton(android.R.string.ok, (d, which) -> switchProfile(profile)) - .setNegativeButton(android.R.string.cancel, null) - .show(); + createDialog( + getString(R.string.profiles_dialog_switch_title), + getString(R.string.profiles_dialog_switch_message, profile.getName()), + null, + ignored -> { + switchProfile(profile); + return true; + } + ).show(); } private void showLogoutDialog(Profile profile) { - String message = getString(R.string.profiles_dialog_logout_message, profile.getName()); - new AlertDialog.Builder(requireContext()) - .setTitle(R.string.profiles_dialog_logout_title) - .setMessage(message) - .setPositiveButton(android.R.string.ok, (d, which) -> logoutProfile(profile)) - .setNegativeButton(android.R.string.cancel, null) - .show(); + createDialog( + getString(R.string.profiles_dialog_logout_title), + getString(R.string.profiles_dialog_logout_message, profile.getName()), + null, + ignored -> { + logoutProfile(profile); + return true; + } + ).show(); } private void showRemoveDialog(Profile profile) { - String message = getString(R.string.profiles_dialog_remove_message, profile.getName()); - new AlertDialog.Builder(requireContext()) - .setTitle(R.string.profiles_dialog_remove_title) - .setMessage(message) - .setPositiveButton(android.R.string.ok, (d, which) -> removeProfile(profile)) - .setNegativeButton(android.R.string.cancel, null) - .show(); + createDialog( + getString(R.string.profiles_dialog_remove_title), + getString(R.string.profiles_dialog_remove_message, profile.getName()), + null, + ignored -> { + removeProfile(profile); + return true; + } + ).show(); } private void addProfile(String profileName) { From 31283d3cbb557f1f46f96a964ec930c60410d90a Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:31:59 -0300 Subject: [PATCH 6/6] Replace deprecated call to navigate back to Home --- .../java/io/netbird/client/ui/profile/ProfilesFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java b/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java index 9296657..f06b75d 100644 --- a/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java +++ b/app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java @@ -229,7 +229,7 @@ private void switchProfile(Profile profile) { loadProfiles(); // Navigate back to home - requireActivity().onBackPressed(); + requireActivity().getOnBackPressedDispatcher().onBackPressed(); } catch (Exception e) { Log.e(TAG, "Failed to switch profile", e); Toast.makeText(requireContext(),