Skip to content
Draft
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
35 changes: 28 additions & 7 deletions app/src/main/java/io/netbird/client/CustomTabURLOpener.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,9 @@ public CustomTabURLOpener(AppCompatActivity activity, OnCustomTabResult resultC
this.context = activity;

this.customTabLauncher = activity.registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult o) {
isOpened = false;
resultCallback.onClosed();
}
new ActivityResultContracts.StartActivityForResult(), o -> {
isOpened = false;
resultCallback.onClosed();
}
);
}
Expand All @@ -58,9 +55,33 @@ public void onLoginSuccess() {
public void open(String url, String userCode) {
isOpened = true;
try {
// Force Google account picker by setting prompt=select_account
// This allows users to choose which Google account to use
String modifiedUrl = url;

// Check if this is an OAuth URL (login.netbird.io, accounts.google.com, or has oauth/authorize)
if (url.contains("/authorize") || url.contains("oauth") || url.contains("accounts.google.com")) {
Uri uri = Uri.parse(url);
Uri.Builder builder = uri.buildUpon().clearQuery();

// Copy all existing parameters except 'prompt'
for (String paramName : uri.getQueryParameterNames()) {
if (!paramName.equals("prompt")) {
for (String value : uri.getQueryParameters(paramName)) {
builder.appendQueryParameter(paramName, value);
}
}
}

// Add prompt=select_account to force account picker
builder.appendQueryParameter("prompt", "select_account");
modifiedUrl = builder.build().toString();
Log.i(TAG, "Replaced prompt parameter with select_account. Modified URL: " + modifiedUrl);
}

CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build();
Intent intent = customTabsIntent.intent;
intent.setData(Uri.parse(url));
intent.setData(Uri.parse(modifiedUrl));
customTabLauncher.launch(intent);
} catch (Exception e) {
Log.e(TAG, "Failed to launch CustomTab: " + e.getMessage());
Expand Down
29 changes: 28 additions & 1 deletion app/src/main/java/io/netbird/client/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ protected void onCreate(Bundle savedInstanceState) {

// Set the listener for menu item selections
navigationView.setNavigationItemSelectedListener(this);

// Update profile menu item with active profile name
updateProfileMenuItem(navigationView);

// On TV, request focus when drawer opens so D-pad navigation works
if (isRunningOnTV) {
Expand Down Expand Up @@ -165,6 +168,10 @@ public void onDrawerClosed(View drawerView) {
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
if (destination.getId() == R.id.nav_home) {
removeToolbarShadow();
// Update profile menu item when returning to home (e.g., after profile switch)
if (binding != null && binding.navView != null) {
updateProfileMenuItem(binding.navView);
}
} else {
resetToolbar();
}
Expand Down Expand Up @@ -253,6 +260,10 @@ public void onStart() {
@Override
protected void onResume() {
super.onResume();
// Update profile menu item when returning to MainActivity
if (binding != null && binding.navView != null) {
updateProfileMenuItem(binding.navView);
}
}

@Override
Expand Down Expand Up @@ -600,6 +611,22 @@ public void onError(String msg) {
}
};

private void updateProfileMenuItem(NavigationView navigationView) {
try {
// Get active profile from ProfileManager instead of reading file
io.netbird.client.tool.ProfileManagerWrapper profileManager =
new io.netbird.client.tool.ProfileManagerWrapper(this);
String activeProfile = profileManager.getActiveProfile();
Menu menu = navigationView.getMenu();
MenuItem profileItem = menu.findItem(R.id.nav_profiles);
if (profileItem != null && activeProfile != null) {
profileItem.setTitle(activeProfile);
}
} catch (Exception e) {
Log.e(LOGTAG, "Failed to update profile menu item", e);
}
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (!isRunningOnTV) {
Expand All @@ -609,7 +636,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.d(LOGTAG, "Key pressed: " + keyCode + " (" + KeyEvent.keyCodeToString(keyCode) + "), repeat: " + event.getRepeatCount());

if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
boolean isOnHomeScreen = navController != null &&
boolean isOnHomeScreen = navController != null &&
navController.getCurrentDestination() != null &&
navController.getCurrentDestination().getId() == R.id.nav_home;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.netbird.client.databinding.FragmentAdvancedBinding;
import io.netbird.client.tool.Logcat;
import io.netbird.client.tool.Preferences;
import io.netbird.client.tool.ProfileManagerWrapper;


public class AdvancedFragment extends Fragment {
Expand Down Expand Up @@ -67,7 +68,14 @@ private void configureForceRelayConnectionSwitch(@NonNull ComponentSwitchBinding
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {

String configFilePath = Preferences.configFile(inflater.getContext());
// Get config path from ProfileManager instead of constructing it
ProfileManagerWrapper profileManager = new ProfileManagerWrapper(inflater.getContext());
String configFilePath;
try {
configFilePath = profileManager.getActiveConfigPath();
} catch (Exception e) {
throw new RuntimeException("Failed to get config path: " + e.getMessage(), e);
}
goPreferences = new io.netbird.gomobile.android.Preferences(configFilePath);

binding = FragmentAdvancedBinding.inflate(inflater, container, false);
Expand Down Expand Up @@ -318,7 +326,14 @@ private boolean isValidPresharedKey(String key) {
}

private void setPreSharedKey(String key, Context context) {
String configFilePath = Preferences.configFile(context);
ProfileManagerWrapper profileManager = new ProfileManagerWrapper(context);
String configFilePath;
try {
configFilePath = profileManager.getActiveConfigPath();
} catch (Exception e) {
Toast.makeText(context, "Failed to get config path: " + e.getMessage(), Toast.LENGTH_LONG).show();
return;
}
io.netbird.gomobile.android.Preferences preferences = new io.netbird.gomobile.android.Preferences(configFilePath);
try {
preferences.setPreSharedKey(key);
Expand All @@ -331,7 +346,14 @@ private void setPreSharedKey(String key, Context context) {
}

private boolean hasPreSharedKey(Context context) {
String configFilePath = Preferences.configFile(context);
ProfileManagerWrapper profileManager = new ProfileManagerWrapper(context);
String configFilePath;
try {
configFilePath = profileManager.getActiveConfigPath();
} catch (Exception e) {
Log.e(LOGTAG, "Failed to get config path", e);
return false;
}
io.netbird.gomobile.android.Preferences preferences = new io.netbird.gomobile.android.Preferences(configFilePath);
try {
return !preferences.getPreSharedKey().isEmpty();
Expand Down
108 changes: 108 additions & 0 deletions app/src/main/java/io/netbird/client/ui/profile/ProfilesAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package io.netbird.client.ui.profile;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

import io.netbird.client.R;
import io.netbird.client.tool.Profile;

public class ProfilesAdapter extends RecyclerView.Adapter<ProfilesAdapter.ProfileViewHolder> {

private final List<Profile> profiles;
private final ProfileActionListener listener;

public interface ProfileActionListener {
void onSwitchProfile(Profile profile);
void onLogoutProfile(Profile profile);
void onRemoveProfile(Profile profile);
}

public ProfilesAdapter(List<Profile> profiles, ProfileActionListener listener) {
this.profiles = profiles;
this.listener = listener;
}

@NonNull
@Override
public ProfileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_profile, parent, false);
return new ProfileViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull ProfileViewHolder holder, int position) {
Profile profile = profiles.get(position);
holder.bind(profile, listener);
}

@Override
public int getItemCount() {
return profiles.size();
}

static class ProfileViewHolder extends RecyclerView.ViewHolder {
private final TextView textName;
private final TextView badgeActive;
private final Button btnSwitch;
private final Button btnLogout;
private final Button btnRemove;

public ProfileViewHolder(@NonNull View itemView) {
super(itemView);
textName = itemView.findViewById(R.id.text_profile_name);
badgeActive = itemView.findViewById(R.id.badge_active);
btnSwitch = itemView.findViewById(R.id.btn_switch);
btnLogout = itemView.findViewById(R.id.btn_logout);
btnRemove = itemView.findViewById(R.id.btn_remove);
}

public void bind(Profile profile, ProfileActionListener listener) {
textName.setText(profile.getName());

if (profile.isActive()) {
badgeActive.setVisibility(View.VISIBLE);
btnSwitch.setEnabled(false);
btnSwitch.setText(R.string.profiles_active);
} else {
badgeActive.setVisibility(View.GONE);
btnSwitch.setEnabled(true);
btnSwitch.setText(R.string.profiles_switch);
}

// Disable remove for default profile
if (profile.getName().equals("default")) {
btnRemove.setEnabled(false);
btnRemove.setAlpha(0.5f);
} else {
btnRemove.setEnabled(true);
btnRemove.setAlpha(1.0f);
}

btnSwitch.setOnClickListener(v -> {
if (!profile.isActive()) {
listener.onSwitchProfile(profile);
}
});

btnLogout.setOnClickListener(v -> {
listener.onLogoutProfile(profile);
});

btnRemove.setOnClickListener(v -> {
if (!profile.getName().equals("default")) {
listener.onRemoveProfile(profile);
}
});
}
}
}
Loading
Loading