Support full MUC configuration form for owners

Stephen Paul Weber created

Change summary

src/main/java/eu/siacs/conversations/entities/Conversation.java     | 128 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java   |  30 
src/main/java/eu/siacs/conversations/ui/adapter/CommandAdapter.java |  40 
src/main/java/eu/siacs/conversations/xmpp/forms/Data.java           |   2 
4 files changed, 189 insertions(+), 11 deletions(-)

Detailed changes

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

@@ -1406,6 +1406,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
         pagerAdapter.startCommand(command, xmppConnectionService);
     }
 
+    public void startMucConfig(XmppConnectionService xmppConnectionService) {
+        pagerAdapter.startMucConfig(xmppConnectionService);
+    }
+
     public boolean switchToSession(final String node) {
         return pagerAdapter.switchToSession(node);
     }
@@ -1574,6 +1578,37 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
             if (mPager != null) mPager.setCurrentItem(getCount() - 1);
         }
 
+        public void startMucConfig(XmppConnectionService xmppConnectionService) {
+            MucConfigSession session = new MucConfigSession(xmppConnectionService);
+            final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+            packet.setTo(Conversation.this.getJid().asBareJid());
+            packet.addChild("query", "http://jabber.org/protocol/muc#owner");
+
+            final TimerTask task = new TimerTask() {
+                @Override
+                public void run() {
+                    if (getAccount().getStatus() != Account.State.ONLINE) {
+                        final TimerTask self = this;
+                        new Timer().schedule(new TimerTask() {
+                            @Override
+                            public void run() {
+                                self.run();
+                            }
+                        }, 1000);
+                    } else {
+                        xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
+                            session.updateWithResponse(iq);
+                        }, 120L);
+                    }
+                }
+            };
+            task.run();
+
+            sessions.add(session);
+            notifyDataSetChanged();
+            if (mPager != null) mPager.setCurrentItem(getCount() - 1);
+        }
+
         public void removeSession(ConversationPage session) {
             sessions.remove(session);
             notifyDataSetChanged();
@@ -3434,6 +3469,99 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
                 return null;
             }
         }
+
+        class MucConfigSession extends CommandSession {
+            MucConfigSession(XmppConnectionService xmppConnectionService) {
+                super("Configure Channel", null, xmppConnectionService);
+            }
+
+            @Override
+            protected void updateWithResponseUiThread(final IqPacket iq) {
+                Timer oldTimer = this.loadingTimer;
+                this.loadingTimer = new Timer();
+                oldTimer.cancel();
+                this.executing = false;
+                this.loading = false;
+                this.loadingHasBeenLong = false;
+                this.responseElement = null;
+                this.fillableFieldCount = 0;
+                this.reported = null;
+                this.response = iq;
+                this.items.clear();
+                this.actionsAdapter.clear();
+                layoutManager.setSpanCount(1);
+
+                final Element query = iq.findChild("query", "http://jabber.org/protocol/muc#owner");
+                if (iq.getType() == IqPacket.TYPE.RESULT && query != null) {
+                    final Data form = Data.parse(query.findChild("x", "jabber:x:data"));
+                    final String title = form.getTitle();
+                    if (title != null) {
+                        mTitle = title;
+                        ConversationPagerAdapter.this.notifyDataSetChanged();
+                    }
+
+                    this.responseElement = form;
+                    setupReported(form.findChild("reported", "jabber:x:data"));
+                    if (mBinding != null) mBinding.form.setLayoutManager(setupLayoutManager());
+
+                    if (actionsAdapter.countExceptCancel() < 1) {
+                        actionsAdapter.add(Pair.create("save", "Save"));
+                    }
+
+                    if (actionsAdapter.getPosition("cancel") < 0) {
+                        actionsAdapter.insert(Pair.create("cancel", "cancel"), 0);
+                    }
+                } else if (iq.getType() == IqPacket.TYPE.RESULT) {
+                    expectingRemoval = true;
+                    removeSession(this);
+                    return;
+                } else {
+                    actionsAdapter.add(Pair.create("close", "close"));
+                }
+
+                notifyDataSetChanged();
+            }
+
+            @Override
+            public synchronized boolean execute(String action) {
+                if ("cancel".equals(action)) {
+                    final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+                    packet.setTo(response.getFrom());
+                    final Element form = packet
+                        .addChild("query", "http://jabber.org/protocol/muc#owner")
+                        .addChild("x", "jabber:x:data");
+                    form.setAttribute("type", "cancel");
+                    xmppConnectionService.sendIqPacket(getAccount(), packet, null);
+                    return true;
+                }
+
+                if (!"save".equals(action)) return true;
+
+                final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
+                packet.setTo(response.getFrom());
+
+                String formType = responseElement == null ? null : responseElement.getAttribute("type");
+                if (responseElement != null &&
+                    responseElement.getName().equals("x") &&
+                    responseElement.getNamespace().equals("jabber:x:data") &&
+                    formType != null && formType.equals("form")) {
+
+                    responseElement.setAttribute("type", "submit");
+                    packet
+                        .addChild("query", "http://jabber.org/protocol/muc#owner")
+                        .addChild(responseElement);
+                }
+
+                executing = true;
+                xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
+                    updateWithResponse(iq);
+                }, 120L);
+
+                loading();
+
+                return false;
+            }
+        }
     }
 
     public static class Thread {

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -3151,8 +3151,7 @@ public class ConversationFragment extends XmppFragment
             binding.commandsView.setOnItemClickListener((parent, view, position, id) -> {
                 if (activity == null) return;
 
-                final Element command = commandAdapter.getItem(position);
-                activity.startCommand(ConversationFragment.this.conversation.getAccount(), command.getAttributeAsJid("jid"), command.getAttribute("node"));
+                commandAdapter.getItem(position).start(activity, ConversationFragment.this.conversation);
             });
             refreshCommands(false);
         }
@@ -3169,6 +3168,11 @@ public class ConversationFragment extends XmppFragment
     protected void refreshCommands(boolean delayShow) {
         if (commandAdapter == null) return;
 
+        final CommandAdapter.MucConfig mucConfig =
+            conversation.getMucOptions().getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER) ?
+            new CommandAdapter.MucConfig() :
+            null;
+
         Jid commandJid = conversation.getContact().resourceWhichSupport(Namespace.COMMANDS);
         if (commandJid == null && conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().hasFeature(Namespace.COMMANDS)) {
             commandJid = conversation.getJid().asBareJid();
@@ -3177,7 +3181,14 @@ public class ConversationFragment extends XmppFragment
             commandJid = conversation.getJid();
         }
         if (commandJid == null) {
-            conversation.hideViewPager();
+            binding.commandsViewProgressbar.setVisibility(View.GONE);
+            if (mucConfig == null) {
+                conversation.hideViewPager();
+            } else {
+                commandAdapter.clear();
+                commandAdapter.add(mucConfig);
+                conversation.showViewPager();
+            }
         } else {
             if (!delayShow) conversation.showViewPager();
             binding.commandsViewProgressbar.setVisibility(View.VISIBLE);
@@ -3185,15 +3196,17 @@ public class ConversationFragment extends XmppFragment
                 if (activity == null) return;
 
                 activity.runOnUiThread(() -> {
+                    binding.commandsViewProgressbar.setVisibility(View.GONE);
+                    commandAdapter.clear();
                     if (iq.getType() == IqPacket.TYPE.RESULT) {
-                        binding.commandsViewProgressbar.setVisibility(View.GONE);
-                        commandAdapter.clear();
                         for (Element child : iq.query().getChildren()) {
                             if (!"item".equals(child.getName()) || !Namespace.DISCO_ITEMS.equals(child.getNamespace())) continue;
-                            commandAdapter.add(child);
+                            commandAdapter.add(new CommandAdapter.Command0050(child));
                         }
                     }
 
+                    if (mucConfig != null) commandAdapter.add(mucConfig);
+
                     if (commandAdapter.getCount() < 1) {
                         conversation.hideViewPager();
                     } else if (delayShow) {
@@ -3353,7 +3366,10 @@ public class ConversationFragment extends XmppFragment
     private Element commandFor(final Jid jid, final String node) {
         if (commandAdapter != null) {
             for (int i = 0; i < commandAdapter.getCount(); i++) {
-                Element command = commandAdapter.getItem(i);
+                final CommandAdapter.Command c = commandAdapter.getItem(i);
+                if (!(c instanceof CommandAdapter.Command0050)) continue;
+
+                final Element command = ((CommandAdapter.Command0050) c).el;
                 final String commandNode = command.getAttribute("node");
                 if (commandNode == null || !commandNode.equals(node)) continue;
 

src/main/java/eu/siacs/conversations/ui/adapter/CommandAdapter.java 🔗

@@ -9,11 +9,13 @@ 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;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.ui.ConversationsActivity;
+import eu.siacs.conversations.ui.XmppActivity;
+import eu.siacs.conversations.xml.Element;
 
-public class CommandAdapter extends ArrayAdapter<Element> {
+public class CommandAdapter extends ArrayAdapter<CommandAdapter.Command> {
 	public CommandAdapter(XmppActivity activity) {
 		super(activity, 0);
 	}
@@ -21,7 +23,37 @@ public class CommandAdapter extends ArrayAdapter<Element> {
 	@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"));
+		binding.command.setText(getItem(position).getName());
 		return binding.getRoot();
 	}
+
+	public interface Command {
+		public String getName();
+		public void start(final ConversationsActivity activity, final Conversation conversation);
+	}
+
+	public static class Command0050 implements Command {
+		public final Element el;
+		public Command0050(Element el) { this.el = el; }
+
+		public String getName() {
+			return el.getAttribute("name");
+		}
+
+		public void start(final ConversationsActivity activity, final Conversation conversation) {
+			activity.startCommand(conversation.getAccount(), el.getAttributeAsJid("jid"), el.getAttribute("node"));
+		}
+	}
+
+	public static class MucConfig implements Command {
+		public MucConfig() { }
+
+		public String getName() {
+			return "⚙️ Configure Channel";
+		}
+
+		public void start(final ConversationsActivity activity, final Conversation conversation) {
+			conversation.startMucConfig(activity.xmppConnectionService);
+		}
+	}
 }