Merge branch 'action-buttons'

Stephen Paul Weber created

* action-buttons:
  Support custom actions defined by a special list-single

Change summary

src/cheogram/res/layout/command_page.xml                        |  1 
src/main/java/eu/siacs/conversations/entities/Conversation.java | 93 ++
src/main/java/eu/siacs/conversations/xmpp/forms/Data.java       |  2 
src/main/java/eu/siacs/conversations/xmpp/forms/Field.java      |  4 
src/main/java/eu/siacs/conversations/xmpp/forms/Option.java     |  2 
5 files changed, 74 insertions(+), 28 deletions(-)

Detailed changes

src/cheogram/res/layout/command_page.xml 🔗

@@ -16,6 +16,7 @@
 
         <GridView
             android:id="@+id/actions"
+            android:background="@color/perpy"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_alignParentStart="true"

src/main/java/eu/siacs/conversations/entities/Conversation.java 🔗

@@ -1,6 +1,7 @@
 package eu.siacs.conversations.entities;
 
 import android.content.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
 import android.database.DataSetObserver;
 import android.graphics.Rect;
@@ -31,6 +32,7 @@ import android.webkit.WebMessage;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.webkit.WebChromeClient;
+import android.util.Pair;
 import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
@@ -93,8 +95,9 @@ 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.Option;
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
+import eu.siacs.conversations.xmpp.forms.Data;
+import eu.siacs.conversations.xmpp.forms.Option;
 import eu.siacs.conversations.xmpp.mam.MamReference;
 import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 
@@ -1615,7 +1618,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                                 }
                                 Element validate = field.el.findChild("validate", "http://jabber.org/protocol/xdata-validate");
                                 if (validate != null) el.addChild(validate);
-                                new ResultFieldViewHolder(row).bind(new Field(el, -1));
+                                new ResultFieldViewHolder(row).bind(new Field(eu.siacs.conversations.xmpp.forms.Field.parse(el), -1));
                             }
                         }
                     }
@@ -1955,7 +1958,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
             }
 
             class Field extends Item {
-                Field(Element el, int viewType) { super(el, viewType); }
+                Field(eu.siacs.conversations.xmpp.forms.Field el, int viewType) { super(el, viewType); }
 
                 @Override
                 public boolean validate() {
@@ -2034,7 +2037,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                         }
                     }
 
-                    return new Field(el, viewType);
+                    return new Field(eu.siacs.conversations.xmpp.forms.Field.parse(el), viewType);
                 }
 
                 return null;
@@ -2066,6 +2069,35 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                 return item;
             }
 
+            class ActionsAdapter extends ArrayAdapter<Pair<String, String>> {
+                protected Context ctx;
+
+                public ActionsAdapter(Context ctx) {
+                    super(ctx, R.layout.simple_list_item);
+                    this.ctx = ctx;
+                }
+
+                @Override
+                public View getView(int position, View convertView, ViewGroup parent) {
+                    View v = super.getView(position, convertView, parent);
+                    TextView tv = (TextView) v.findViewById(android.R.id.text1);
+                    tv.setGravity(Gravity.CENTER);
+                    tv.setText(getItem(position).second);
+                    int resId = ctx.getResources().getIdentifier("action_" + getItem(position).first, "string" , ctx.getPackageName());
+                    if (resId != 0) tv.setText(ctx.getResources().getString(resId));
+                    tv.setTextColor(ContextCompat.getColor(ctx, R.color.white));
+                    tv.setBackgroundColor(UIHelper.getColorForName(getItem(position).first));
+                    return v;
+                }
+
+                public int getPosition(String s) {
+                    for(int i = 0; i < getCount(); i++) {
+                        if (getItem(i).first.equals(s)) return i;
+                    }
+                    return -1;
+                }
+            }
+
             final int TYPE_ERROR = 1;
             final int TYPE_NOTE = 2;
             final int TYPE_WEB = 3;
@@ -2089,7 +2121,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
             protected List<Field> reported = null;
             protected SparseArray<Item> items = new SparseArray<>();
             protected XmppConnectionService xmppConnectionService;
-            protected ArrayAdapter<String> actionsAdapter;
+            protected ActionsAdapter actionsAdapter;
             protected GridLayoutManager layoutManager;
             protected WebView actionToWebview = null;
 
@@ -2099,19 +2131,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                 mNode = node;
                 this.xmppConnectionService = xmppConnectionService;
                 if (mPager != null) setupLayoutManager();
-                actionsAdapter = new ArrayAdapter<String>(xmppConnectionService, R.layout.simple_list_item) {
-                    @Override
-                    public View getView(int position, View convertView, ViewGroup parent) {
-                        View v = super.getView(position, convertView, parent);
-                        TextView tv = (TextView) v.findViewById(android.R.id.text1);
-                        tv.setGravity(Gravity.CENTER);
-                        int resId = xmppConnectionService.getResources().getIdentifier("action_" + tv.getText() , "string" , xmppConnectionService.getPackageName());
-                        if (resId != 0) tv.setText(xmppConnectionService.getResources().getString(resId));
-                        tv.setTextColor(ContextCompat.getColor(xmppConnectionService, R.color.white));
-                        tv.setBackgroundColor(UIHelper.getColorForName(tv.getText().toString()));
-                        return v;
-                    }
-                };
+                actionsAdapter = new ActionsAdapter(xmppConnectionService);
                 actionsAdapter.registerDataSetObserver(new DataSetObserver() {
                     @Override
                     public void onChanged() {
@@ -2152,11 +2172,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                                 if (!el.getNamespace().equals("http://jabber.org/protocol/commands")) continue;
                                 if (action.getName().equals("execute")) continue;
 
-                                actionsAdapter.add(action.getName());
+                                actionsAdapter.add(Pair.create(action.getName(), action.getName()));
                             }
                         }
                         if (el.getName().equals("x") && el.getNamespace().equals("jabber:x:data")) {
-                            String title = el.findChildContent("title", "jabber:x:data");
+                            Data form = Data.parse(el);
+                            String title = form.getTitle();
                             if (title != null) {
                                 mTitle = title;
                                 ConversationPagerAdapter.this.notifyDataSetChanged();
@@ -2167,6 +2188,15 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                                 setupReported(el.findChild("reported", "jabber:x:data"));
                                 if (mBinding != null) mBinding.form.setLayoutManager(setupLayoutManager());
                             }
+
+                            eu.siacs.conversations.xmpp.forms.Field actionList = form.getFieldByName("http://jabber.org/protocol/commands#actions");
+                            if (actionList != null) {
+                                actionsAdapter.clear();
+
+                                for (Option action : actionList.getOptions()) {
+                                    actionsAdapter.add(Pair.create(action.getValue(), action.toString()));
+                                }
+                            }
                             break;
                         }
                         if (el.getName().equals("x") && el.getNamespace().equals("jabber:x:oob")) {
@@ -2193,20 +2223,20 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                     if (command.getAttribute("status").equals("executing") && actionsAdapter.getCount() < 1) {
                         // No actions have been given, but we are not done?
                         // This is probably a spec violation, but we should do *something*
-                        actionsAdapter.add("execute");
+                        actionsAdapter.add(Pair.create("execute", "execute"));
                     }
 
                     if (!actionsAdapter.isEmpty()) {
                         if (command.getAttribute("status").equals("completed") || command.getAttribute("status").equals("canceled")) {
-                            actionsAdapter.add("close");
+                            actionsAdapter.add(Pair.create("close", "close"));
                         } else if (actionsAdapter.getPosition("cancel") < 0) {
-                            actionsAdapter.insert("cancel", 0);
+                            actionsAdapter.insert(Pair.create("cancel", "cancel"), 0);
                         }
                     }
                 }
 
                 if (actionsAdapter.isEmpty()) {
-                    actionsAdapter.add("close");
+                    actionsAdapter.add(Pair.create("close", "close"));
                 }
 
                 notifyDataSetChanged();
@@ -2237,6 +2267,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                         if (el.getName().equals("field")) {
                             String type = el.getAttribute("type");
                             if (type != null && type.equals("hidden")) continue;
+                            if (el.getAttribute("var") != null && el.getAttribute("var").equals("http://jabber.org/protocol/commands#actions")) continue;
                         }
 
                         if (el.getName().equals("reported") || el.getName().equals("item")) {
@@ -2270,6 +2301,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                             if (el.getName().equals("field")) {
                                 String type = el.getAttribute("type");
                                 if (type != null && type.equals("hidden")) continue;
+                                if (el.getAttribute("var") != null && el.getAttribute("var").equals("http://jabber.org/protocol/commands#actions")) continue;
                             }
 
                             if (el.getName().equals("reported") || el.getName().equals("item")) {
@@ -2407,7 +2439,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
             }
 
             public boolean execute(int actionPosition) {
-                return execute(actionsAdapter.getItem(actionPosition));
+                return execute(actionsAdapter.getItem(actionPosition).first);
             }
 
             public boolean execute(String action) {
@@ -2429,7 +2461,6 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                 final Element c = packet.addChild("command", Namespace.COMMANDS);
                 c.setAttribute("node", mNode);
                 c.setAttribute("sessionid", command.getAttribute("sessionid"));
-                c.setAttribute("action", action);
 
                 String formType = responseElement == null ? null : responseElement.getAttribute("type");
                 if (!action.equals("cancel") &&
@@ -2439,6 +2470,13 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                     responseElement.getNamespace().equals("jabber:x:data") &&
                     formType != null && formType.equals("form")) {
 
+                    Data form = Data.parse(responseElement);
+                    eu.siacs.conversations.xmpp.forms.Field actionList = form.getFieldByName("http://jabber.org/protocol/commands#actions");
+                    if (actionList != null) {
+                        actionList.setValue(action);
+                        c.setAttribute("action", "execute");
+                    }
+
                     responseElement.setAttribute("type", "submit");
                     Element rsm = responseElement.findChild("set", "http://jabber.org/protocol/rsm");
                     if (rsm != null) {
@@ -2446,9 +2484,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                         max.setContent("1000");
                         rsm.addChild(max);
                     }
+
                     c.addChild(responseElement);
                 }
 
+                if (c.getAttribute("action") == null) c.setAttribute("action", action);
+
                 xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
                     getView().post(() -> {
                         updateWithResponse(iq);