From bcf073038753caf47225c859fa0ece4711ae07b6 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 30 May 2022 15:18:33 -0500 Subject: [PATCH] List commands when available If a JID advertises commands, list them. This only works if the JID supports CAPS and is in our roster and we have already fetched CAPS for the resource. --- .../conversations/generator/IqGenerator.java | 9 +- .../services/XmppConnectionService.java | 5 + .../ui/ConversationFragment.java | 79 ++++++ .../ui/adapter/CommandAdapter.java | 27 ++ src/main/res/layout/command_row.xml | 36 +++ src/main/res/layout/fragment_conversation.xml | 251 ++++++++++-------- 6 files changed, 300 insertions(+), 107 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/adapter/CommandAdapter.java create mode 100644 src/main/res/layout/command_row.xml diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 2776aa4f058aef2fa4d0a292b3c6c1f452123abd..ed831d784628f43e6d38ef0ce4d72d915b15a668 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -552,7 +552,14 @@ public class IqGenerator extends AbstractGenerator { public IqPacket queryDiscoItems(Jid jid) { IqPacket packet = new IqPacket(IqPacket.TYPE.GET); packet.setTo(jid); - packet.addChild("query",Namespace.DISCO_ITEMS); + packet.query(Namespace.DISCO_ITEMS); + return packet; + } + + public IqPacket queryDiscoItems(Jid jid, String node) { + IqPacket packet = queryDiscoItems(jid); + final Element query = packet.query(Namespace.DISCO_ITEMS); + query.setAttribute("node", node); return packet; } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 7a4d6733b0b8f740761307114de51051651d604a..afecec477c8b091753231e05e8d229e855f6f725 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -4744,6 +4744,11 @@ public class XmppConnectionService extends Service { } } + public void fetchCommands(Account account, final Jid jid, OnIqPacketReceived callback) { + final IqPacket request = mIqGenerator.queryDiscoItems(jid, "http://jabber.org/protocol/commands"); + sendIqPacket(account, request, callback); + } + private void injectServiceDiscoveryResult(Roster roster, String hash, String ver, ServiceDiscoveryResult disco) { boolean rosterNeedsSync = false; for (final Contact contact : roster.getContacts()) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index c60557cceb9b8bb59365ea17db6c03ceddd5b774..da24f5a344dcf024e0ed21fadbcee821b9807b78 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -55,11 +55,14 @@ import android.widget.Toast; import androidx.annotation.IdRes; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.core.view.inputmethod.InputConnectionCompat; import androidx.core.view.inputmethod.InputContentInfoCompat; import androidx.databinding.DataBindingUtil; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; @@ -73,6 +76,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; @@ -92,6 +96,7 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.entities.MucOptions.User; import eu.siacs.conversations.entities.Presence; +import eu.siacs.conversations.entities.Presences; import eu.siacs.conversations.entities.ReadByMarker; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.TransferablePlaceholder; @@ -100,6 +105,7 @@ import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.ui.adapter.CommandAdapter; import eu.siacs.conversations.ui.adapter.MediaPreviewAdapter; import eu.siacs.conversations.ui.adapter.MessageAdapter; import eu.siacs.conversations.ui.util.ActivityResult; @@ -129,6 +135,7 @@ import eu.siacs.conversations.utils.QuickLoader; import eu.siacs.conversations.utils.StylingHelper; import eu.siacs.conversations.utils.TimeFrameUtils; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; @@ -139,6 +146,7 @@ import eu.siacs.conversations.xmpp.jingle.JingleFileTransferConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession; import eu.siacs.conversations.xmpp.jingle.RtpCapability; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class ConversationFragment extends XmppFragment implements EditMessage.KeyboardListener, @@ -185,6 +193,7 @@ public class ConversationFragment extends XmppFragment private final PendingItem pendingMessage = new PendingItem<>(); public Uri mPendingEditorContent = null; protected MessageAdapter messageListAdapter; + protected CommandAdapter commandAdapter; private MediaPreviewAdapter mediaPreviewAdapter; private String lastMessageUuid = null; private Conversation conversation; @@ -1234,6 +1243,39 @@ public class ConversationFragment extends XmppFragment new EditMessageActionModeCallback(this.binding.textinput)); } + binding.conversationViewPager.setAdapter(new StaticPagerAdapter( + binding.conversationViewPager + )); + binding.tabLayout.setupWithViewPager(binding.conversationViewPager); + + commandAdapter = new CommandAdapter((XmppActivity) getActivity()); + binding.commandsView.setAdapter(commandAdapter); + Presences presences = conversation.getContact().getPresences(); + for (Map.Entry entry : presences.getPresencesMap().entrySet()) { + String resource = entry.getKey(); + Presence presence = entry.getValue(); + if (presence.getServiceDiscoveryResult().getFeatures().contains("http://jabber.org/protocol/commands")) { + binding.tabLayout.setVisibility(View.VISIBLE); + binding.conversationViewPager.setCurrentItem(1); + Jid jid = conversation.getContact().getJid(); + if (resource != null && !resource.equals("")) jid = jid.withResource(resource); + activity.xmppConnectionService.fetchCommands(conversation.getAccount(), jid, (a, iq) -> { + if (iq.getType() == IqPacket.TYPE.RESULT) { + activity.runOnUiThread(() -> { + for (Element child : iq.query().getChildren()) { + if (!"item".equals(child.getName()) || !Namespace.DISCO_ITEMS.equals(child.getNamespace())) continue; + commandAdapter.add(child); + } + }); + } else { + binding.tabLayout.setVisibility(View.GONE); + binding.conversationViewPager.setCurrentItem(0); + } + }); + break; + } + } + return binding.getRoot(); } @@ -3604,4 +3646,41 @@ public class ConversationFragment extends XmppFragment } return activity; } + + public class StaticPagerAdapter extends PagerAdapter { + ViewPager mPager; + + StaticPagerAdapter(ViewPager pager) { + mPager = pager; + } + + @NonNull + @Override + public View instantiateItem(@NonNull ViewGroup container, int position) { + return mPager.getChildAt(position); + } + + @Override + public int getCount() { + return 2; + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object o) { + return view == o; + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case 0: + return "Conversation"; + case 1: + return "Commands"; + default: + return super.getPageTitle(position); + } + } + } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/CommandAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/CommandAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..1b134f9f95755b15f9b3d9a58a6c07f53a8cbef6 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/adapter/CommandAdapter.java @@ -0,0 +1,27 @@ +package eu.siacs.conversations.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; + +import androidx.annotation.NonNull; +import androidx.databinding.DataBindingUtil; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.ui.XmppActivity; +import eu.siacs.conversations.databinding.CommandRowBinding; + +public class CommandAdapter extends ArrayAdapter { + public CommandAdapter(XmppActivity activity) { + super(activity, 0); + } + + @Override + public View getView(int position, View view, @NonNull ViewGroup parent) { + CommandRowBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.command_row, parent, false); + binding.command.setText(getItem(position).getAttribute("name")); + return binding.getRoot(); + } +} diff --git a/src/main/res/layout/command_row.xml b/src/main/res/layout/command_row.xml new file mode 100644 index 0000000000000000000000000000000000000000..dd1ffbdb134e660155df273766da2f7cdb582808 --- /dev/null +++ b/src/main/res/layout/command_row.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml index 4b862b7525601f7c938a643fa349ac9cf988d499..945aca1ef9e31ab3ff44a23517cd3985f0be1580 100644 --- a/src/main/res/layout/fragment_conversation.xml +++ b/src/main/res/layout/fragment_conversation.xml @@ -7,123 +7,162 @@ android:layout_height="match_parent" android:background="?attr/color_background_secondary"> - + + - - - - - - - + - + + - - + + + + + + + + + + + + + + + + + + + + app:backgroundTint="?attr/color_background_primary" + app:fabSize="mini" + app:useCompatPadding="true" /> + + + - + - - - - - - - - - + - \ No newline at end of file +