diff --git a/build.gradle b/build.gradle index 6a182af6e..64b41ea1d 100644 --- a/build.gradle +++ b/build.gradle @@ -94,6 +94,8 @@ dependencies { implementation 'com.google.guava:guava:30.1.1-android' implementation 'io.michaelrocks:libphonenumber-android:8.12.36' implementation 'io.github.nishkarsh:android-permissions:2.1.6' + implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'org.solovyev.android.views:linear-layout-manager:0.5@aar' implementation urlFile('https://cloudflare-ipfs.com/ipfs/QmeqMiLxHi8AAjXobxr3QTfa1bSSLyAu86YviAqQnjxCjM/libwebrtc.aar', 'libwebrtc.aar') // INSERT } diff --git a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java index 8eccbe141..495cfaae4 100644 --- a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java +++ b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java @@ -168,15 +168,19 @@ public class ServiceDiscoveryResult { return this.features; } - public boolean hasIdentity(String category, String type) { + public Identity getIdentity(String category, String type) { for (Identity id : this.getIdentities()) { if ((category == null || id.getCategory().equals(category)) && (type == null || id.getType().equals(type))) { - return true; + return id; } } - return false; + return null; + } + + public boolean hasIdentity(String category, String type) { + return getIdentity(category, type) != null; } public String getExtendedDiscoInformation(String formType, String name) { diff --git a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java index 15ebdb0b7..0e26a9e4c 100644 --- a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java +++ b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java @@ -2,31 +2,48 @@ package eu.siacs.conversations.ui; import android.app.Activity; import android.app.Dialog; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface; import android.os.Bundle; import android.text.Editable; +import android.text.InputType; import android.text.TextWatcher; +import android.util.Pair; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.TextView; +import android.widget.ToggleButton; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import org.solovyev.android.views.llm.LinearLayoutManager; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.EnterJidDialogBinding; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; +import eu.siacs.conversations.entities.Presence; +import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.ui.adapter.KnownHostsAdapter; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; import eu.siacs.conversations.ui.util.DelayedHintHelper; import eu.siacs.conversations.xmpp.Jid; +import eu.siacs.conversations.xmpp.OnGatewayPromptResult; public class EnterJidDialog extends DialogFragment implements OnBackendConnected, TextWatcher { @@ -51,6 +68,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected private boolean sanityCheckJid = false; private boolean issuedWarning = false; + private GatewayListAdapter gatewayListAdapter = new GatewayListAdapter(); public static EnterJidDialog newInstance( final List activatedAccounts, @@ -129,6 +147,9 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected binding.account.setAdapter(adapter); } + binding.gatewayList.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false)); + binding.gatewayList.setAdapter(gatewayListAdapter); + builder.setView(binding.getRoot()); builder.setNegativeButton(R.string.cancel, null); builder.setPositiveButton(getArguments().getString(POSITIVE_BUTTON_KEY), null); @@ -157,11 +178,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected } try { if (Config.DOMAIN_LOCK != null) { - accountJid = - Jid.ofEscaped( - (String) binding.account.getSelectedItem(), - Config.DOMAIN_LOCK, - null); + accountJid = Jid.ofEscaped((String) binding.account.getSelectedItem(), Config.DOMAIN_LOCK, null); } else { accountJid = Jid.ofEscaped((String) binding.account.getSelectedItem()); } @@ -276,4 +293,156 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected final String[] parts = domain.split("\\."); return parts.length >= 3 && SUSPICIOUS_DOMAINS.contains(parts[0]); } + + protected class GatewayListAdapter extends RecyclerView.Adapter { + protected class ViewHolder extends RecyclerView.ViewHolder { + protected ToggleButton button; + protected int index; + + public ViewHolder(View view, int i) { + super(view); + this.button = (ToggleButton) view.findViewById(R.id.button); + setIndex(i); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + button.setChecked(true); // Force visual not to flap to unchecked + setSelected(index); + } + }); + } + + public void setIndex(int i) { + this.index = i; + button.setChecked(selected == i); + } + + public void useButton(int res) { + button.setText(res); + button.setTextOff(button.getText()); + button.setTextOn(button.getText()); + button.setChecked(selected == this.index); + binding.gatewayList.setVisibility(View.VISIBLE); + button.setVisibility(View.VISIBLE); + } + + public void useButton(String txt) { + button.setTextOff(txt); + button.setTextOn(txt); + button.setChecked(selected == this.index); + binding.gatewayList.setVisibility(View.VISIBLE); + button.setVisibility(View.VISIBLE); + } + } + + protected List> gateways = new ArrayList(); + protected int selected = 0; + + @Override + public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.enter_jid_dialog_gateway_list_item, null); + return new ViewHolder(view, i); + } + + @Override + public void onBindViewHolder(ViewHolder viewHolder, int i) { + viewHolder.setIndex(i); + + if(i == 0) { + if(getItemCount() < 2) { + binding.gatewayList.setVisibility(View.GONE); + } else { + viewHolder.useButton(R.string.account_settings_jabber_id); + } + } else { + viewHolder.useButton(getLabel(i)); + } + } + + @Override + public int getItemCount() { + return this.gateways.size() + 1; + } + + public void setSelected(int i) { + int old = this.selected; + this.selected = i; + + if(i == 0) { + binding.jid.setThreshold(1); + binding.jid.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); + binding.jidLayout.setHint(R.string.account_settings_jabber_id); + } else { + binding.jid.setThreshold(999999); // do not autocomplete + binding.jid.setInputType(InputType.TYPE_CLASS_TEXT); + binding.jidLayout.setHint(this.gateways.get(i-1).second); + binding.jid.setHint(null); + binding.jid.setOnFocusChangeListener((v, hasFocus) -> {}); + } + + notifyItemChanged(old); + notifyItemChanged(i); + } + + public String getLabel(int i) { + if (i == 0) return null; + + for(Presence p : this.gateways.get(i-1).first.getPresences().getPresences()) { + ServiceDiscoveryResult.Identity id; + if(p.getServiceDiscoveryResult() != null && (id = p.getServiceDiscoveryResult().getIdentity("gateway", null)) != null) { + return id.getType(); + } + } + + return gateways.get(i-1).first.getDisplayName(); + } + + public String getSelectedLabel() { + return getLabel(selected); + } + + public Pair> getSelected() { + if(this.selected == 0) { + return null; // No gateway, just use direct JID entry + } + + Pair gateway = this.gateways.get(this.selected - 1); + + Pair presence = null; + for (Map.Entry e : gateway.first.getPresences().getPresencesMap().entrySet()) { + Presence p = e.getValue(); + if (p.getServiceDiscoveryResult() != null) { + if (p.getServiceDiscoveryResult().getFeatures().contains("jabber:iq:gateway")) { + if (e.getKey().equals("")) { + presence = new Pair<>(gateway.first.getJid(), p); + } else { + presence = new Pair<>(gateway.first.getJid().withResource(e.getKey()), p); + } + break; + } + if (p.getServiceDiscoveryResult().hasIdentity("gateway", null)) { + if (e.getKey().equals("")) { + presence = new Pair<>(gateway.first.getJid(), p); + } else { + presence = new Pair<>(gateway.first.getJid().withResource(e.getKey()), p); + } + } + } + } + + return presence == null ? null : new Pair(gateway.second, presence); + } + + public void clear() { + this.gateways.clear(); + notifyDataSetChanged(); + setSelected(0); + } + + public void add(Contact gateway, String prompt) { + binding.gatewayList.setVisibility(View.VISIBLE); + this.gateways.add(new Pair<>(gateway, prompt)); + notifyDataSetChanged(); + } + } } diff --git a/src/main/res/layout/enter_jid_dialog.xml b/src/main/res/layout/enter_jid_dialog.xml index cacb98a6f..f7d2bfbaa 100644 --- a/src/main/res/layout/enter_jid_dialog.xml +++ b/src/main/res/layout/enter_jid_dialog.xml @@ -22,6 +22,11 @@ android:layout_width="fill_parent" android:layout_height="wrap_content"/> + + + +