From c1b48fbc7774bdf9ff8af42764f508beed0635e6 Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:07:35 -0300 Subject: [PATCH 01/16] Fix hint color --- app/src/main/res/layout/fragment_server.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/fragment_server.xml b/app/src/main/res/layout/fragment_server.xml index de2a570..452a5b4 100644 --- a/app/src/main/res/layout/fragment_server.xml +++ b/app/src/main/res/layout/fragment_server.xml @@ -63,6 +63,7 @@ android:inputType="textUri" android:background="@drawable/edit_text_white" android:padding="12dp" + android:textColorHint="@color/nb_txt_light" app:layout_constraintTop_toBottomOf="@id/text_setup_key_label" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" From 0295aa02e23346ca9f0b1716c1e0effbff2c058b Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:20:55 -0300 Subject: [PATCH 02/16] Fix setup key color --- app/src/main/res/layout/fragment_server.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/fragment_server.xml b/app/src/main/res/layout/fragment_server.xml index 452a5b4..3f12434 100644 --- a/app/src/main/res/layout/fragment_server.xml +++ b/app/src/main/res/layout/fragment_server.xml @@ -63,6 +63,7 @@ android:inputType="textUri" android:background="@drawable/edit_text_white" android:padding="12dp" + android:textColor="@color/nb_txt" android:textColorHint="@color/nb_txt_light" app:layout_constraintTop_toBottomOf="@id/text_setup_key_label" app:layout_constraintStart_toStartOf="parent" From 6d2d6669e744f70aca4cac99da68796e10f8bcff Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Tue, 18 Nov 2025 16:21:51 -0300 Subject: [PATCH 03/16] Add "(Optional)" to Setup key field's title --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 65ae49f..fa5ee14 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,7 +48,7 @@ Server https://example-api.domain.com:443 - Setup key + Setup key (Optional) key Invalid setup key format From 95b5c1428b1d1421bc7b9f1272c6089f94bd0b8f Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:08:58 -0300 Subject: [PATCH 04/16] Add ChangeServerFragment UiState and ViewModel --- .../server/ChangeServerFragmentUiState.java | 54 +++++++ .../server/ChangeServerFragmentViewModel.java | 134 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentUiState.java create mode 100644 app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentViewModel.java diff --git a/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentUiState.java b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentUiState.java new file mode 100644 index 0000000..51e8637 --- /dev/null +++ b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentUiState.java @@ -0,0 +1,54 @@ +package io.netbird.client.ui.server; + +public class ChangeServerFragmentUiState { + public final boolean isUiEnabled; + public final boolean isSetupKeyInvalid; + public final boolean isOperationSuccessful; + public final String errorMessage; + public final boolean shouldDisplayWarningDialog; + + public ChangeServerFragmentUiState(Builder builder) { + this.isSetupKeyInvalid = builder.isSetupKeyInvalid; + this.isUiEnabled = builder.isUiEnabled; + this.isOperationSuccessful = builder.isOperationSuccessful; + this.shouldDisplayWarningDialog = builder.shouldDisplayWarningDialog; + this.errorMessage = builder.errorMessage; + } + + public static class Builder { + private boolean isUiEnabled = true; + private boolean isSetupKeyInvalid = false; + private boolean isOperationSuccessful = false; + private String errorMessage; + private boolean shouldDisplayWarningDialog = false; + + public Builder isUiEnabled(boolean isUiEnabled) { + this.isUiEnabled = isUiEnabled; + return this; + } + + public Builder isSetupKeyInvalid(boolean isSetupKeyInvalid) { + this.isSetupKeyInvalid = isSetupKeyInvalid; + return this; + } + + public Builder isOperationSuccessful(boolean isOperationSuccessful) { + this.isOperationSuccessful = isOperationSuccessful; + return this; + } + + public Builder errorMessage(String errorMessage) { + this.errorMessage = errorMessage; + return this; + } + + public Builder shouldDisplayWarningDialog(boolean shouldDisplayWarningDialog) { + this.shouldDisplayWarningDialog = shouldDisplayWarningDialog; + return this; + } + + public ChangeServerFragmentUiState build() { + return new ChangeServerFragmentUiState(this); + } + } +} diff --git a/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentViewModel.java b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentViewModel.java new file mode 100644 index 0000000..f04700a --- /dev/null +++ b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentViewModel.java @@ -0,0 +1,134 @@ +package io.netbird.client.ui.server; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import java.util.Optional; +import java.util.UUID; + +import io.netbird.gomobile.android.Android; +import io.netbird.gomobile.android.Auth; +import io.netbird.gomobile.android.ErrListener; +import io.netbird.gomobile.android.SSOListener; + +public class ChangeServerFragmentViewModel extends ViewModel { + public interface Operation { + void execute(); + } + + private final MutableLiveData uiState; + private final String configFilePath; + private final String defaultManagementServerAddress; + private final String deviceName; + private final Operation stopEngineCommand; + + public ChangeServerFragmentViewModel(String configFilePath, String defaultManagementServerAddress, String deviceName, Operation stopEngineCommand) { + this.configFilePath = configFilePath; + this.defaultManagementServerAddress = defaultManagementServerAddress; + this.deviceName = deviceName; + this.stopEngineCommand = stopEngineCommand; + + var state = new ChangeServerFragmentUiState.Builder() + .isUiEnabled(true) + .shouldDisplayWarningDialog(true) + .build(); + this.uiState = new MutableLiveData<>(state); + } + + private boolean isValidSetupKey(String setupKey) { + try { + UUID.fromString(setupKey); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + private Optional getAuthenticator(String managementServerAddress) { + Auth authenticator; + + try { + authenticator = Android.newAuth(configFilePath, managementServerAddress); + } catch (Exception e) { + emitErrorState(e); + return Optional.empty(); + } + + return Optional.ofNullable(authenticator); + } + + private void emitSuccessState() { + var state = new ChangeServerFragmentUiState.Builder() + .isUiEnabled(false) + .isOperationSuccessful(true) + .build(); + + uiState.postValue(state); + } + + private void emitErrorState(Exception e) { + var state = new ChangeServerFragmentUiState.Builder() + .isUiEnabled(true) + .errorMessage(e.getMessage()) + .build(); + + uiState.postValue(state); + } + + private void emitErrorState(ChangeServerFragmentUiState state) { + uiState.postValue(state); + } + + private void disableUi() { + uiState.postValue(new ChangeServerFragmentUiState.Builder() + .isUiEnabled(false) + .build()); + } + + public LiveData getUiState() { + return uiState; + } + + public void changeManagementServerAddress(String managementServerAddress) { + disableUi(); + + getAuthenticator(managementServerAddress).ifPresent((authenticator) -> authenticator.saveConfigIfSSOSupported(new SSOListener() { + @Override + public void onError(Exception e) { + emitErrorState(e); + } + + @Override + public void onSuccess(boolean isSSOEnabled) { + stopEngineCommand.execute(); + emitSuccessState(); + } + })); + } + + public void loginWithSetupKey(String managementServerAddress, String setupKey) { + disableUi(); + + if (!isValidSetupKey(setupKey)) { + emitErrorState(new ChangeServerFragmentUiState.Builder() + .isSetupKeyInvalid(true) + .isUiEnabled(true) + .build()); + return; + } + + getAuthenticator(managementServerAddress).ifPresent((authenticator) -> authenticator.loginWithSetupKeyAndSaveConfig(new ErrListener() { + @Override + public void onError(Exception e) { + emitErrorState(e); + } + + @Override + public void onSuccess() { + stopEngineCommand.execute(); + emitSuccessState(); + } + }, setupKey, deviceName)); + } +} From be0527ff78415b1076b8764af7b9d28e169516bb Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Tue, 18 Nov 2025 19:47:39 -0300 Subject: [PATCH 05/16] Change Setup Key group in fragment_server.xml to always be visible --- app/src/main/res/layout/fragment_server.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_server.xml b/app/src/main/res/layout/fragment_server.xml index 3f12434..58d4a4e 100644 --- a/app/src/main/res/layout/fragment_server.xml +++ b/app/src/main/res/layout/fragment_server.xml @@ -38,7 +38,6 @@ android:id="@+id/setup_key_group" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:visibility="gone" app:constraint_referenced_ids="text_setup_key_label,edit_text_setup_key" /> Date: Tue, 18 Nov 2025 19:48:48 -0300 Subject: [PATCH 06/16] Use ViewModel in ChangeServerFragment --- .../ui/server/ChangeServerFragment.java | 157 +++++++++++++----- .../server/ChangeServerFragmentViewModel.java | 21 ++- 2 files changed, 135 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java index dbdb96c..37dc563 100644 --- a/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java +++ b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java @@ -9,29 +9,32 @@ import android.view.View; import android.view.ViewGroup; -import java.util.UUID; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.viewmodel.MutableCreationExtras; + +import java.util.UUID; import io.netbird.client.R; import io.netbird.client.ServiceAccessor; import io.netbird.client.databinding.FragmentServerBinding; +import io.netbird.client.tool.Preferences; import io.netbird.gomobile.android.Android; import io.netbird.gomobile.android.Auth; -import io.netbird.client.tool.Preferences; import io.netbird.gomobile.android.ErrListener; import io.netbird.gomobile.android.SSOListener; public class ChangeServerFragment extends Fragment { - public static final String HideAlertBundleArg="hideAlert"; + public static final String HideAlertBundleArg = "hideAlert"; private FragmentServerBinding binding; private ServiceAccessor serviceAccessor; + private ChangeServerFragmentViewModel viewModel; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -50,55 +53,128 @@ public void onAttach(@NonNull Context context) { } } + private void mapStateToUi(ChangeServerFragmentUiState uiState) { + if (uiState.shouldDisplayWarningDialog) { + showConfirmChangeServerDialog(); + } + + if (uiState.isUiEnabled) { + enableUIElements(); + } else { + disableUIElements(); + } + + if (uiState.errorMessage != null && !uiState.errorMessage.isEmpty()) { + binding.editTextServer.setError(uiState.errorMessage); + binding.editTextServer.requestFocus(); + } + + if (uiState.isSetupKeyInvalid) { + binding.editTextSetupKey.setError(requireContext().getString(R.string.change_server_error_invalid_setup_key)); + binding.editTextSetupKey.requestFocus(); + } + + if (uiState.isOperationSuccessful) { + showSuccessDialog(requireContext()); + } + } + @SuppressLint("NonConstantResourceId") @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - boolean hideAlert = false; - if (getArguments() != null) { - hideAlert = getArguments().getBoolean("hideAlert", false); - } + MutableCreationExtras extras = new MutableCreationExtras(); + extras.set(ChangeServerFragmentViewModel.CONFIG_FILE_PATH_KEY, Preferences.configFile(requireContext())); + extras.set(ChangeServerFragmentViewModel.DEVICE_NAME_KEY, deviceName()); + extras.set(ChangeServerFragmentViewModel.STOP_ENGINE_COMMAND_KEY, + (ChangeServerFragmentViewModel.Operation) () -> serviceAccessor.stopEngine()); - if (!hideAlert) { - showConfirmChangeServerDialog(); - } + viewModel = new ViewModelProvider(getViewModelStore(), + ViewModelProvider.Factory.from(ChangeServerFragmentViewModel.initializer), extras) + .get(ChangeServerFragmentViewModel.class); - binding.btnUseNetbird.setOnClickListener(v -> { - disableUIElements(); - binding.editTextServer.setText(Preferences.defaultServer()); - updateServer(view.getContext(), Preferences.defaultServer()); - }); + viewModel.getUiState().observe(getViewLifecycleOwner(), this::mapStateToUi); - binding.btnChangeServer.setOnClickListener(v->{ - if (binding.editTextServer.getText().toString().trim().isEmpty()) { + binding.btnChangeServer.setOnClickListener(v -> { + String managementServerUri = binding.editTextServer.getText().toString().trim(); + String setupKey = binding.editTextSetupKey.getText().toString().trim(); + + if (managementServerUri.isEmpty() && setupKey.isEmpty()) { return; } - disableUIElements(); + if (!managementServerUri.isEmpty() && !setupKey.isEmpty()) { + viewModel.loginWithSetupKey(managementServerUri, setupKey); + } else if (!managementServerUri.isEmpty()) { + viewModel.changeManagementServerAddress(managementServerUri); + } else { + managementServerUri = Preferences.defaultServer(); - if (binding.setupKeyGroup.getVisibility() == View.VISIBLE) { - String setupKey = binding.editTextSetupKey.getText().toString().trim(); - if(setupKey.isEmpty()) { - binding.editTextSetupKey.setError(v.getContext().getString(R.string.change_server_error_invalid_setup_key)); - binding.editTextSetupKey.requestFocus(); - enableUIElements(); - return; - } - if (!isValidSetupKey(setupKey)) { - binding.editTextSetupKey.setError(v.getContext().getString(R.string.change_server_error_invalid_setup_key)); - binding.editTextSetupKey.requestFocus(); - enableUIElements(); - return; - } - String serverAddress = binding.editTextServer.getText().toString().trim(); - loginWithSetupKey(v.getContext(), serverAddress, setupKey); + binding.editTextServer.setText(managementServerUri); + viewModel.loginWithSetupKey(managementServerUri, setupKey); + } + }); + + binding.btnUseNetbird.setOnClickListener(v -> { + String setupKey = binding.editTextSetupKey.getText().toString().trim(); + String managementServerUri = Preferences.defaultServer(); + + binding.editTextServer.setText(managementServerUri); + + if (setupKey.isEmpty()) { + viewModel.changeManagementServerAddress(managementServerUri); } else { - // Setup key is empty; update server instead - String serverAddress = binding.editTextServer.getText().toString().trim(); - updateServer(v.getContext(), serverAddress); + viewModel.loginWithSetupKey(managementServerUri, setupKey); } }); + +// boolean hideAlert = false; +// if (getArguments() != null) { +// hideAlert = getArguments().getBoolean("hideAlert", false); +// } +// +// if (!hideAlert) { +// showConfirmChangeServerDialog(); +// } +// +// binding.btnUseNetbird.setOnClickListener(v -> { +// disableUIElements(); +// binding.editTextServer.setText(Preferences.defaultServer()); +// updateServer(view.getContext(), Preferences.defaultServer()); +// }); +// +// binding.setupKeyGroup.setVisibility(View.VISIBLE); +// +// binding.btnChangeServer.setOnClickListener(v -> { +// if (binding.editTextServer.getText().toString().trim().isEmpty()) { +// return; +// } +// +// disableUIElements(); +// +// if (binding.setupKeyGroup.getVisibility() == View.VISIBLE) { +// String setupKey = binding.editTextSetupKey.getText().toString().trim(); +// if (setupKey.isEmpty()) { +// binding.editTextSetupKey.setError(v.getContext().getString(R.string.change_server_error_invalid_setup_key)); +// binding.editTextSetupKey.requestFocus(); +// enableUIElements(); +// return; +// } +// if (!isValidSetupKey(setupKey)) { +// binding.editTextSetupKey.setError(v.getContext().getString(R.string.change_server_error_invalid_setup_key)); +// binding.editTextSetupKey.requestFocus(); +// enableUIElements(); +// return; +// } +// String serverAddress = binding.editTextServer.getText().toString().trim(); +// loginWithSetupKey(v.getContext(), serverAddress, setupKey); +// } else { +// // Setup key is empty; update server instead +// String serverAddress = binding.editTextServer.getText().toString().trim(); +// updateServer(v.getContext(), serverAddress); +// } +// }); } @Override @@ -211,7 +287,7 @@ public void onSuccess(boolean sso) { activity.runOnUiThread(() -> { if (binding == null) return; - if(!sso) { + if (!sso) { binding.setupKeyGroup.setVisibility(View.VISIBLE); } else { binding.setupKeyGroup.setVisibility(View.GONE); @@ -238,13 +314,14 @@ public void onSuccess(boolean sso) { } private void disableUIElements() { - if(binding == null) return; + if (binding == null) return; binding.editTextServer.setEnabled(false); binding.editTextSetupKey.setEnabled(false); binding.btnChangeServer.setText(R.string.change_server_verifying); binding.btnChangeServer.setEnabled(false); binding.btnUseNetbird.setVisibility(View.GONE); } + private void enableUIElements() { FragmentActivity activity = getActivity(); if (activity == null) return; diff --git a/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentViewModel.java b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentViewModel.java index f04700a..4d08c66 100644 --- a/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentViewModel.java +++ b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragmentViewModel.java @@ -3,6 +3,8 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import androidx.lifecycle.viewmodel.CreationExtras; +import androidx.lifecycle.viewmodel.ViewModelInitializer; import java.util.Optional; import java.util.UUID; @@ -19,13 +21,11 @@ public interface Operation { private final MutableLiveData uiState; private final String configFilePath; - private final String defaultManagementServerAddress; private final String deviceName; private final Operation stopEngineCommand; - public ChangeServerFragmentViewModel(String configFilePath, String defaultManagementServerAddress, String deviceName, Operation stopEngineCommand) { + public ChangeServerFragmentViewModel(String configFilePath, String deviceName, Operation stopEngineCommand) { this.configFilePath = configFilePath; - this.defaultManagementServerAddress = defaultManagementServerAddress; this.deviceName = deviceName; this.stopEngineCommand = stopEngineCommand; @@ -36,6 +36,21 @@ public ChangeServerFragmentViewModel(String configFilePath, String defaultManage this.uiState = new MutableLiveData<>(state); } + public static final CreationExtras.Key CONFIG_FILE_PATH_KEY = new CreationExtras.Key<>() {}; + public static final CreationExtras.Key DEVICE_NAME_KEY = new CreationExtras.Key<>() {}; + public static final CreationExtras.Key STOP_ENGINE_COMMAND_KEY = new CreationExtras.Key<>() {}; + + static final ViewModelInitializer initializer = new ViewModelInitializer<>( + ChangeServerFragmentViewModel.class, + creationExtras -> { + String configFilePath = creationExtras.get(CONFIG_FILE_PATH_KEY); + String deviceName = creationExtras.get(DEVICE_NAME_KEY); + Operation stopEngineOperation = creationExtras.get(STOP_ENGINE_COMMAND_KEY); + + return new ChangeServerFragmentViewModel(configFilePath, deviceName, stopEngineOperation); + } + ); + private boolean isValidSetupKey(String setupKey) { try { UUID.fromString(setupKey); From f575994c333f5aa5c5821cfa8b3529925642a7e7 Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:38:27 -0300 Subject: [PATCH 07/16] Add click listener to textSetupKeyLabel To toggle editTextSetupKey visibility Also add plus and minus icons --- .../ui/server/ChangeServerFragment.java | 35 ++++++++++++++++--- app/src/main/res/drawable/add_24px.xml | 10 ++++++ app/src/main/res/drawable/remove_24px.xml | 10 ++++++ app/src/main/res/layout/fragment_server.xml | 12 ++++--- app/src/main/res/values/strings.xml | 2 +- 5 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 app/src/main/res/drawable/add_24px.xml create mode 100644 app/src/main/res/drawable/remove_24px.xml diff --git a/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java index 37dc563..ac7b4f9 100644 --- a/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java +++ b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java @@ -2,6 +2,7 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -12,6 +13,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.ViewModelProvider; @@ -79,6 +81,12 @@ private void mapStateToUi(ChangeServerFragmentUiState uiState) { } } + private void setBounds(Drawable drawable) { + if (drawable == null) return; + + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + } + @SuppressLint("NonConstantResourceId") @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { @@ -96,6 +104,23 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat viewModel.getUiState().observe(getViewLifecycleOwner(), this::mapStateToUi); + Drawable minusIcon = ContextCompat.getDrawable(requireContext(), R.drawable.remove_24px); + Drawable plusIcon = ContextCompat.getDrawable(requireContext(), R.drawable.add_24px); + setBounds(minusIcon); + setBounds(plusIcon); + + binding.textSetupKeyLabel.setOnClickListener(v -> { + if (binding.editTextSetupKey.getVisibility() == View.VISIBLE) { + binding.textSetupKeyLabel.setCompoundDrawables(plusIcon, null, null, null); + + binding.editTextSetupKey.setText(""); + binding.editTextSetupKey.setVisibility(View.GONE); + } else { + binding.textSetupKeyLabel.setCompoundDrawables(minusIcon, null, null, null); + binding.editTextSetupKey.setVisibility(View.VISIBLE); + } + }); + binding.btnChangeServer.setOnClickListener(v -> { String managementServerUri = binding.editTextServer.getText().toString().trim(); String setupKey = binding.editTextSetupKey.getText().toString().trim(); @@ -287,11 +312,11 @@ public void onSuccess(boolean sso) { activity.runOnUiThread(() -> { if (binding == null) return; - if (!sso) { - binding.setupKeyGroup.setVisibility(View.VISIBLE); - } else { - binding.setupKeyGroup.setVisibility(View.GONE); - } +// if (!sso) { +// binding.setupKeyGroup.setVisibility(View.VISIBLE); +// } else { +// binding.setupKeyGroup.setVisibility(View.GONE); +// } }); enableUIElements(); diff --git a/app/src/main/res/drawable/add_24px.xml b/app/src/main/res/drawable/add_24px.xml new file mode 100644 index 0000000..627c677 --- /dev/null +++ b/app/src/main/res/drawable/add_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/remove_24px.xml b/app/src/main/res/drawable/remove_24px.xml new file mode 100644 index 0000000..e0b42ed --- /dev/null +++ b/app/src/main/res/drawable/remove_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_server.xml b/app/src/main/res/layout/fragment_server.xml index 58d4a4e..5366de0 100644 --- a/app/src/main/res/layout/fragment_server.xml +++ b/app/src/main/res/layout/fragment_server.xml @@ -34,11 +34,11 @@ app:layout_constraintEnd_toEndOf="parent" android:layout_marginTop="8dp" /> - + + + + + Server https://example-api.domain.com:443 - Setup key (Optional) + Add this device with setup key key Invalid setup key format From 755c0e63b1cf53d2998e75e7668237b39ab46919 Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:36:03 -0300 Subject: [PATCH 08/16] Fix change_server_setup_key --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b63beda..3c19a83 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,7 +48,7 @@ Server https://example-api.domain.com:443 - Add this device with setup key + Add this device with a setup key key Invalid setup key format From c501e17a9a93290a48a740001760f67dada59d69 Mon Sep 17 00:00:00 2001 From: Diego Romar <18450339+doromaraujo@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:16:30 -0300 Subject: [PATCH 09/16] Add usage of view group to toggle setup key widgets' visibility --- .../ui/server/ChangeServerFragment.java | 56 ++++++++++++++++++- app/src/main/res/layout/fragment_server.xml | 47 ++++++++++++---- 2 files changed, 90 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java index ac7b4f9..fe44ffb 100644 --- a/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java +++ b/app/src/main/java/io/netbird/client/ui/server/ChangeServerFragment.java @@ -1,5 +1,8 @@ package io.netbird.client.ui.server; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.drawable.Drawable; @@ -9,6 +12,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -87,6 +91,49 @@ private void setBounds(Drawable drawable) { drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); } + public void expandView(final View view) { + // Measure the view to get its target height + view.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + final int targetHeight = view.getMeasuredHeight(); + + // Set initial height to 0 and make it visible + view.getLayoutParams().height = 0; + view.setVisibility(View.VISIBLE); + + ValueAnimator animator = ValueAnimator.ofInt(0, targetHeight); + animator.setDuration(1000); + animator.setInterpolator(new AccelerateDecelerateInterpolator()); + + animator.addUpdateListener(animation -> { + view.getLayoutParams().height = (int) animation.getAnimatedValue(); + view.requestLayout(); + }); + + animator.start(); + } + + public void collapseView(final View view) { + final int initialHeight = view.getMeasuredHeight(); + + ValueAnimator animator = ValueAnimator.ofInt(initialHeight, 0); + animator.setDuration(1000); + animator.setInterpolator(new AccelerateDecelerateInterpolator()); + + animator.addUpdateListener(animation -> { + view.getLayoutParams().height = (int) animation.getAnimatedValue(); + view.requestLayout(); + }); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(View.GONE); // Hide the view after animation + } + }); + + animator.start(); + } + @SuppressLint("NonConstantResourceId") @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { @@ -110,14 +157,17 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat setBounds(plusIcon); binding.textSetupKeyLabel.setOnClickListener(v -> { - if (binding.editTextSetupKey.getVisibility() == View.VISIBLE) { + if (binding.setupKeyGroup.getVisibility() == View.VISIBLE) { binding.textSetupKeyLabel.setCompoundDrawables(plusIcon, null, null, null); binding.editTextSetupKey.setText(""); - binding.editTextSetupKey.setVisibility(View.GONE); + binding.editTextSetupKey.setError(null); + binding.setupKeyGroup.setVisibility(View.GONE); +// collapseView(binding.setupKeyGroup); } else { binding.textSetupKeyLabel.setCompoundDrawables(minusIcon, null, null, null); - binding.editTextSetupKey.setVisibility(View.VISIBLE); + binding.setupKeyGroup.setVisibility(View.VISIBLE); +// expandView(binding.setupKeyGroup); } }); diff --git a/app/src/main/res/layout/fragment_server.xml b/app/src/main/res/layout/fragment_server.xml index 5366de0..32902db 100644 --- a/app/src/main/res/layout/fragment_server.xml +++ b/app/src/main/res/layout/fragment_server.xml @@ -3,6 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:tools="http://schemas.android.com/tools" android:padding="16dp"> @@ -20,7 +21,7 @@ - - - - - + + + + + + +