Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 90 additions & 54 deletions app/src/main/java/io/netbird/client/ui/profile/ProfilesFragment.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package io.netbird.client.ui.profile;

import static android.view.View.GONE;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
Expand All @@ -26,11 +30,10 @@

public class ProfilesFragment extends Fragment {
private static final String TAG = "ProfilesFragment";

private RecyclerView recyclerView;
private ProfilesAdapter adapter;
private ProfileManagerWrapper profileManager;
private List<Profile> profiles = new ArrayList<>();
private final List<Profile> profiles = new ArrayList<>();

@Nullable
@Override
Expand Down Expand Up @@ -76,41 +79,68 @@ private void loadProfiles() {
adapter.notifyDataSetChanged();
}

private void showAddDialog() {
View dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_simple_alert_message, null);
final EditText input = new EditText(requireContext());
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)
.create();
interface DialogCallback {
// Return true to dismiss dialog.
boolean onConfirm(@Nullable String inputText);
}

dialog.show();
@SuppressLint("InflateParams")
private AlertDialog createDialog(String title, String message, @Nullable String inputHint, DialogCallback callback) {
boolean hasInput = inputHint != null;

// Set click listener after show() to prevent auto-dismiss on validation failure
dialog.getButton(AlertDialog.BUTTON_POSITIVE).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;
}
View dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_simple_edit_text, null);

// 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;
}
TextView txtTitle = dialogView.findViewById(R.id.text_title_dialog);
txtTitle.setText(title);

addProfile(profileName);
dialog.dismiss();
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() {
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();
}

/**
Expand All @@ -130,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) {
Expand Down Expand Up @@ -193,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(),
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/res/drawable/focus_highlight.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
<stroke
android:width="3dp"
android:color="@color/white" />
<corners android:radius="8dp" />
<corners android:radius="2dp" />
</shape>
</item>

<!-- Default state (not focused) - no border -->
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="8dp" />
<corners android:radius="2dp" />
</shape>
</item>
</selector>
Expand Down
81 changes: 81 additions & 0 deletions app/src/main/res/layout/dialog_simple_edit_text.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_nb_bg"
android:maxWidth="560dp"
android:minWidth="280dp"
android:padding="24dp">

<TextView
android:id="@+id/text_title_dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text=""
tools:text="Title title title" />

<TextView
android:id="@+id/text_label_dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_title_dialog"
android:text=""
tools:text="Label label label label" />

<EditText
android:id="@+id/edit_text_dialog"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_white"
android:padding="12dp"
android:textColor="@color/nb_txt"
android:textColorHint="@color/nb_txt_light"
android:focusable="true"
android:focusableInTouchMode="true"
android:foreground="@drawable/focus_highlight"
android:autofillHints=""
android:inputType="text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_label_dialog"
tools:text="input input input input input input input input input input input"
/>

<com.google.android.material.button.MaterialButton
android:id="@+id/btn_cancel_dialog"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:focusable="true"
android:focusableInTouchMode="false"
android:text="@android:string/cancel"
android:textColor="@color/nb_orange"
app:layout_constraintEnd_toStartOf="@id/btn_ok_dialog"
app:layout_constraintTop_toBottomOf="@id/edit_text_dialog"
tools:text="@android:string/cancel" />

<com.google.android.material.button.MaterialButton
android:id="@+id/btn_ok_dialog"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:focusable="true"
android:focusableInTouchMode="false"
android:text="@android:string/ok"
android:textColor="@color/nb_orange"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/edit_text_dialog"
tools:text="@android:string/ok" />

</androidx.constraintlayout.widget.ConstraintLayout>
Loading