provide the same fab submenu for both tabs. rename tab to bookmark

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/generator/IqGenerator.java            |  14 
src/main/java/eu/siacs/conversations/parser/PresenceParser.java            |   4 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java   |  31 
src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java     |   1 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java          |   1 
src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java |  24 
src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java  |  20 
src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java     | 297 
src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java          |   4 
src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java     | 178 
src/main/java/eu/siacs/conversations/utils/CryptoHelper.java               |   3 
src/main/res/drawable-hdpi/ic_close_white_24dp.png                         |   0 
src/main/res/drawable-hdpi/ic_public_white_24dp.png                        |   0 
src/main/res/drawable-mdpi/ic_close_white_24dp.png                         |   0 
src/main/res/drawable-mdpi/ic_public_white_24dp.png                        |   0 
src/main/res/drawable-xhdpi/ic_close_white_24dp.png                        |   0 
src/main/res/drawable-xhdpi/ic_public_white_24dp.png                       |   0 
src/main/res/drawable-xxhdpi/ic_close_white_24dp.png                       |   0 
src/main/res/drawable-xxhdpi/ic_public_white_24dp.png                      |   0 
src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png                      |   0 
src/main/res/drawable-xxxhdpi/ic_public_white_24dp.png                     |   0 
src/main/res/layout/activity_start_conversation.xml                        |   4 
src/main/res/layout/create_conference_dialog.xml                           |   2 
src/main/res/layout/create_public_channel_dialog.xml                       |  55 
src/main/res/layout/dialog_join_conference.xml                             |   4 
src/main/res/layout/enter_jid_dialog.xml                                   |   2 
src/main/res/menu/conference_context.xml                                   |   3 
src/main/res/menu/start_conversation_fab_submenu.xml                       |  19 
src/main/res/menu/start_conversation_group_fab.xml                         |  11 
src/main/res/values/strings.xml                                            |  26 
src/main/res/values/themes.xml                                             |   4 
31 files changed, 564 insertions(+), 143 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/generator/IqGenerator.java πŸ”—

@@ -456,7 +456,7 @@ public class IqGenerator extends AbstractGenerator {
 		return packet;
 	}
 
-	public static Bundle defaultRoomConfiguration() {
+	public static Bundle defaultGroupChatConfiguration() {
 		Bundle options = new Bundle();
 		options.putString("muc#roomconfig_persistentroom", "1");
 		options.putString("muc#roomconfig_membersonly", "1");
@@ -468,6 +468,18 @@ public class IqGenerator extends AbstractGenerator {
 		return options;
 	}
 
+	public static Bundle defaultChannelConfiguration() {
+		Bundle options = new Bundle();
+		options.putString("muc#roomconfig_persistentroom", "1");
+		options.putString("muc#roomconfig_membersonly", "0");
+		options.putString("muc#roomconfig_publicroom", "1");
+		options.putString("muc#roomconfig_whois", "moderators");
+		options.putString("muc#roomconfig_enablearchiving", "1"); //prosody
+		options.putString("mam", "1"); //ejabberd community
+		options.putString("muc#roomconfig_mam","1"); //ejabberd saas
+		return options;
+	}
+
 	public IqPacket requestPubsubConfiguration(Jid jid, String node) {
 		return pubsubConfiguration(jid, node, null);
 	}

src/main/java/eu/siacs/conversations/parser/PresenceParser.java πŸ”—

@@ -2,7 +2,6 @@ package eu.siacs.conversations.parser;
 
 import android.util.Log;
 
-import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -11,7 +10,6 @@ import eu.siacs.conversations.Config;
 import eu.siacs.conversations.crypto.PgpEngine;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Message;
@@ -99,7 +97,7 @@ public class PresenceParser extends AbstractParser implements
 									+mucOptions.getConversation().getJid().asBareJid()
 									+"' created. pushing default configuration");
 							mXmppConnectionService.pushConferenceConfiguration(mucOptions.getConversation(),
-									IqGenerator.defaultRoomConfiguration(),
+									IqGenerator.defaultGroupChatConfiguration(),
 									null);
 						}
 						if (mXmppConnectionService.getPgpEngine() != null) {

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java πŸ”—

@@ -2424,6 +2424,9 @@ public class XmppConnectionService extends Service {
 					if (mucOptions.nonanonymous() && !mucOptions.membersOnly() && !conversation.getBooleanAttribute("accept_non_anonymous", false)) {
 					    mucOptions.setError(MucOptions.Error.NON_ANONYMOUS);
 					    updateConversationUi();
+                        if (onConferenceJoined != null) {
+                            onConferenceJoined.onConferenceJoined(conversation);
+                        }
 					    return;
                     }
 
@@ -2707,6 +2710,32 @@ public class XmppConnectionService extends Service {
 		return null;
 	}
 
+
+	public void createPublicChannel(final Account account, final String name, final Jid address, final UiCallback<Conversation> callback) {
+        joinMuc(findOrCreateConversation(account, address, true, false, true), conversation -> {
+            final Bundle configuration = IqGenerator.defaultChannelConfiguration();
+            if (!TextUtils.isEmpty(name)) {
+                configuration.putString("muc#roomconfig_roomname", name);
+            }
+            pushConferenceConfiguration(conversation, configuration, new OnConfigurationPushed() {
+                @Override
+                public void onPushSucceeded() {
+                    saveConversationAsBookmark(conversation, name);
+                    callback.success(conversation);
+                }
+
+                @Override
+                public void onPushFailed() {
+                    if (conversation.getMucOptions().getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
+                        callback.error(R.string.unable_to_set_channel_configuration, conversation);
+                    } else {
+                        callback.error(R.string.joined_an_existing_channel, conversation);
+                    }
+                }
+            });
+        });
+    }
+
 	public boolean createAdhocConference(final Account account,
 	                                     final String name,
 	                                     final Iterable<Jid> jids,
@@ -2726,7 +2755,7 @@ public class XmppConnectionService extends Service {
 				joinMuc(conversation, new OnConferenceJoined() {
 					@Override
 					public void onConferenceJoined(final Conversation conversation) {
-						final Bundle configuration = IqGenerator.defaultRoomConfiguration();
+						final Bundle configuration = IqGenerator.defaultGroupChatConfiguration();
 						if (!TextUtils.isEmpty(name)) {
 							configuration.putString("muc#roomconfig_roomname", name);
 						}

src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java πŸ”—

@@ -474,6 +474,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
         } else {
             account = mConversation.getAccount().getJid().asBareJid().toString();
         }
+        setTitle(mucOptions.isPrivateAndNonAnonymous() ? R.string.action_muc_details : R.string.channel_details);
         this.binding.editMucNameButton.setVisibility((self.getAffiliation().ranks(MucOptions.Affiliation.OWNER) || mucOptions.canChangeSubject()) ? View.VISIBLE : View.GONE);
         this.binding.detailsAccount.setText(getString(R.string.using_account, account));
         this.binding.jid.setText(mConversation.getJid().asBareJid().toEscapedString());

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java πŸ”—

@@ -952,6 +952,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
             if (conversation.getMode() == Conversation.MODE_MULTI) {
                 menuContactDetails.setVisible(false);
                 menuInviteContact.setVisible(conversation.getMucOptions().canInvite());
+                menuMucDetails.setTitle(conversation.getMucOptions().isPrivateAndNonAnonymous() ? R.string.action_muc_details : R.string.channel_details);
             } else {
                 menuContactDetails.setVisible(!this.conversation.withSelf());
                 menuMucDetails.setVisible(false);

src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java πŸ”—

@@ -55,6 +55,7 @@ import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.FragmentConversationsOverviewBinding;
 import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Conversational;
 import eu.siacs.conversations.ui.adapter.ConversationAdapter;
 import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
 import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
@@ -133,14 +134,23 @@ public class ConversationsOverviewFragment extends XmppFragment {
 			if (activity instanceof OnConversationArchived) {
 				((OnConversationArchived) activity).onConversationArchived(swipedConversation.peek());
 			}
-			boolean isMuc = swipedConversation.peek().getMode() == Conversation.MODE_MULTI;
-			int title = isMuc ? R.string.title_undo_swipe_out_muc : R.string.title_undo_swipe_out_conversation;
+			final Conversation c = swipedConversation.peek();
+			final int title;
+			if (c.getMode() == Conversational.MODE_MULTI) {
+				if (c.getMucOptions().isPrivateAndNonAnonymous()) {
+					title = R.string.title_undo_swipe_out_group_chat;
+				} else {
+					title = R.string.title_undo_swipe_out_channel;
+				}
+			} else {
+				title = R.string.title_undo_swipe_out_conversation;
+			}
 
 			final Snackbar snackbar = Snackbar.make(binding.list, title, 5000)
 					.setAction(R.string.undo, v -> {
 						pendingActionHelper.undo();
-						Conversation c = swipedConversation.pop();
-						conversationsAdapter.insert(c, position);
+						Conversation conversation = swipedConversation.pop();
+						conversationsAdapter.insert(conversation, position);
 						if (formerlySelected) {
 							if (activity instanceof OnConversationSelected) {
 								((OnConversationSelected) activity).onConversationSelected(c);
@@ -167,9 +177,9 @@ public class ConversationsOverviewFragment extends XmppFragment {
 				if (snackbar.isShownOrQueued()) {
 					snackbar.dismiss();
 				}
-				Conversation c = swipedConversation.pop();
-				if(c != null){
-					if (!c.isRead() && c.getMode() == Conversation.MODE_SINGLE) {
+				final Conversation conversation = swipedConversation.pop();
+				if(conversation != null){
+					if (!conversation.isRead() && conversation.getMode() == Conversation.MODE_SINGLE) {
 						return;
 					}
 					activity.xmppConnectionService.archiveConversation(c);

src/main/java/eu/siacs/conversations/ui/CreateConferenceDialog.java β†’ src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java πŸ”—

@@ -1,18 +1,13 @@
 package eu.siacs.conversations.ui;
 
 import android.app.Dialog;
+import android.content.Context;
 import android.databinding.DataBindingUtil;
+import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.v4.app.DialogFragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
 import android.support.v7.app.AlertDialog;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.EditText;
 import android.widget.Spinner;
-import android.widget.TextView;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -21,14 +16,14 @@ import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.CreateConferenceDialogBinding;
 import eu.siacs.conversations.ui.util.DelayedHintHelper;
 
-public class CreateConferenceDialog extends DialogFragment {
+public class CreatePrivateGroupChatDialog extends DialogFragment {
 
     private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list";
     private CreateConferenceDialogListener mListener;
 
-    public static CreateConferenceDialog newInstance(List<String> accounts) {
-        CreateConferenceDialog dialog = new CreateConferenceDialog();
-        Bundle bundle =  new Bundle();
+    public static CreatePrivateGroupChatDialog newInstance(List<String> accounts) {
+        CreatePrivateGroupChatDialog dialog = new CreatePrivateGroupChatDialog();
+        Bundle bundle = new Bundle();
         bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList<String>) accounts);
         dialog.setArguments(bundle);
         return dialog;
@@ -44,7 +39,7 @@ public class CreateConferenceDialog extends DialogFragment {
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-        builder.setTitle(R.string.dialog_title_create_conference);
+        builder.setTitle(R.string.create_private_group_chat);
         CreateConferenceDialogBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.create_conference_dialog, null, false);
         ArrayList<String> mActivatedAccounts = getArguments().getStringArrayList(ACCOUNTS_LIST_KEY);
         StartConversationActivity.populateAccountSpinner(getActivity(), mActivatedAccounts, binding.account);
@@ -59,6 +54,7 @@ public class CreateConferenceDialog extends DialogFragment {
         return builder.create();
     }
 
+
     public interface CreateConferenceDialogListener {
         void onCreateDialogPositiveClick(Spinner spinner, String subject);
     }

src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java πŸ”—

@@ -0,0 +1,297 @@
+package eu.siacs.conversations.ui;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AlertDialog;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.Spinner;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.CreateConferenceDialogBinding;
+import eu.siacs.conversations.databinding.CreatePublicChannelDialogBinding;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.XmppConnectionService;
+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.utils.CryptoHelper;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import rocks.xmpp.addr.Jid;
+
+public class CreatePublicChannelDialog extends DialogFragment implements OnBackendConnected {
+
+    private static final char[] FORBIDDEN = new char[]{'\u0022','&','\'','/',':','<','>','@'};
+
+    private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list";
+    private CreatePublicChannelDialogListener mListener;
+    private KnownHostsAdapter knownHostsAdapter;
+    private boolean jidWasModified = false;
+    private boolean nameEntered = false;
+    private boolean skipTetxWatcher = false;
+    private static final SecureRandom RANDOM = new SecureRandom();
+
+    public static CreatePublicChannelDialog newInstance(List<String> accounts) {
+        CreatePublicChannelDialog dialog = new CreatePublicChannelDialog();
+        Bundle bundle = new Bundle();
+        bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList<String>) accounts);
+        dialog.setArguments(bundle);
+        return dialog;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        setRetainInstance(true);
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        jidWasModified = savedInstanceState != null && savedInstanceState.getBoolean("jid_was_modified_false", false);
+        nameEntered = savedInstanceState != null && savedInstanceState.getBoolean("name_entered", false);
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setTitle(R.string.create_public_channel);
+        final CreatePublicChannelDialogBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.create_public_channel_dialog, null, false);
+        binding.account.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                updateJidSuggestion(binding);
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {
+
+            }
+        });
+        binding.jid.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                if (skipTetxWatcher) {
+                    return;
+                }
+                if (jidWasModified) {
+                    jidWasModified = !TextUtils.isEmpty(s);
+                } else {
+                    jidWasModified = !s.toString().equals(getJidSuggestion(binding));
+                }
+            }
+        });
+        updateInputs(binding,false);
+        ArrayList<String> mActivatedAccounts = getArguments().getStringArrayList(ACCOUNTS_LIST_KEY);
+        StartConversationActivity.populateAccountSpinner(getActivity(), mActivatedAccounts, binding.account);
+        builder.setView(binding.getRoot());
+        builder.setPositiveButton(nameEntered ? R.string.create : R.string.next, null);
+        builder.setNegativeButton(nameEntered ? R.string.back : R.string.cancel, null);
+        DelayedHintHelper.setHint(R.string.channel_bare_jid_example, binding.jid);
+        this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
+        binding.jid.setAdapter(knownHostsAdapter);
+        final AlertDialog dialog = builder.create();
+        binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> {
+            submit(dialog, binding);
+            return true;
+        });
+        dialog.setOnShowListener(dialogInterface -> {
+            dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> goBack(dialog, binding));
+            dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> submit(dialog, binding));
+        });
+        return dialog;
+    }
+
+    private void updateJidSuggestion(CreatePublicChannelDialogBinding binding) {
+        if (jidWasModified) {
+            return;
+        }
+        String jid = getJidSuggestion(binding);
+        skipTetxWatcher = true;
+        binding.jid.setText(jid);
+        skipTetxWatcher = false;
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        outState.putBoolean("jid_was_modified",jidWasModified);
+        outState.putBoolean("name_entered", nameEntered);
+        super.onSaveInstanceState(outState);
+    }
+
+    private static String getJidSuggestion(CreatePublicChannelDialogBinding binding) {
+        final Account account = StartConversationActivity.getSelectedAccount(binding.getRoot().getContext(), binding.account);
+        final XmppConnection connection = account == null ? null : account.getXmppConnection();
+        if (connection == null) {
+            return "";
+        }
+        final Editable nameText = binding.groupChatName.getText();
+        final String name = nameText == null ? "" : nameText.toString().trim();
+        final String domain = connection.getMucServer();
+        if (domain == null) {
+            return "";
+        }
+        final String localpart = clean(name);
+        if (TextUtils.isEmpty(localpart)) {
+            return "";
+        } else {
+            try {
+                return Jid.of(localpart, domain, null).toEscapedString();
+            } catch (IllegalArgumentException e) {
+                return Jid.of(CryptoHelper.pronounceable(RANDOM), domain, null).toEscapedString();
+            }
+        }
+    }
+
+    private static String clean(String name) {
+        for(char c : FORBIDDEN) {
+            name = name.replace(String.valueOf(c),"");
+        }
+        return name.replaceAll("\\s+","-");
+    }
+
+    private void goBack(AlertDialog dialog, CreatePublicChannelDialogBinding binding) {
+        if (nameEntered) {
+            nameEntered = false;
+            updateInputs(binding, true);
+            updateButtons(dialog);
+        } else {
+            dialog.dismiss();
+        }
+    }
+
+    private void submit(AlertDialog dialog, CreatePublicChannelDialogBinding binding) {
+        final Context context = binding.getRoot().getContext();
+        final Editable nameText = binding.groupChatName.getText();
+        final String name = nameText == null ? "" : nameText.toString().trim();
+        final Editable addressText = binding.jid.getText();
+        final String address = addressText == null ? "" : addressText.toString().trim();
+        if (nameEntered) {
+            binding.nameLayout.setError(null);
+            if (address.isEmpty()) {
+                binding.xmppAddressLayout.setError(context.getText(R.string.please_enter_xmpp_address));
+            } else {
+                final Jid jid;
+                try {
+                    jid = Jid.ofEscaped(address);
+                } catch (IllegalArgumentException e) {
+                    binding.xmppAddressLayout.setError(context.getText(R.string.invalid_jid));
+                    return;
+                }
+                final Account account = StartConversationActivity.getSelectedAccount(context, binding.account);
+                if (account == null) {
+                    return;
+                }
+                final XmppConnectionService service = ((XmppActivity )context).xmppConnectionService;
+                if (service != null && service.findFirstMuc(jid) != null) {
+                    binding.xmppAddressLayout.setError(context.getString(R.string.channel_already_exists));
+                    return;
+                }
+                mListener.onCreatePublicChannel(account, name, jid);
+                dialog.dismiss();
+            }
+        } else {
+            binding.xmppAddressLayout.setError(null);
+            if (name.isEmpty()) {
+                binding.nameLayout.setError(context.getText(R.string.please_enter_name));
+            } else if (StartConversationActivity.isValidJid(name)){
+                binding.nameLayout.setError(context.getText(R.string.this_is_an_xmpp_address));
+            } else {
+                binding.nameLayout.setError(null);
+                nameEntered = true;
+                updateInputs(binding, true);
+                updateButtons(dialog);
+                binding.jid.setText("");
+                binding.jid.append(getJidSuggestion(binding));
+            }
+        }
+    }
+
+
+    private void updateInputs(CreatePublicChannelDialogBinding binding, boolean requestFocus) {
+        binding.xmppAddressLayout.setVisibility(nameEntered ? View.VISIBLE : View.GONE);
+        binding.nameLayout.setVisibility(nameEntered ? View.GONE : View.VISIBLE);
+        if (!requestFocus) {
+            return;
+        }
+        if (nameEntered) {
+            binding.xmppAddressLayout.requestFocus();
+        } else {
+            binding.nameLayout.requestFocus();
+        }
+    }
+
+    private void updateButtons(AlertDialog dialog) {
+        final Button positive = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
+        final Button negative = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
+        positive.setText(nameEntered ? R.string.create : R.string.next);
+        negative.setText(nameEntered ? R.string.back : R.string.cancel);
+    }
+
+    @Override
+    public void onBackendConnected() {
+        refreshKnownHosts();
+    }
+
+    private void refreshKnownHosts() {
+        Activity activity = getActivity();
+        if (activity instanceof XmppActivity) {
+            Collection<String> hosts = ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts();
+            this.knownHostsAdapter.refresh(hosts);
+        }
+    }
+
+    public interface CreatePublicChannelDialogListener {
+        void onCreatePublicChannel(Account account, String name, Jid address);
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        try {
+            mListener = (CreatePublicChannelDialogListener) context;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(context.toString()
+                    + " must implement CreateConferenceDialogListener");
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        final Activity activity = getActivity();
+        if (activity instanceof XmppActivity && ((XmppActivity) activity).xmppConnectionService != null) {
+            refreshKnownHosts();
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        Dialog dialog = getDialog();
+        if (dialog != null && getRetainInstance()) {
+            dialog.setDismissMessage(null);
+        }
+        super.onDestroyView();
+    }
+}

src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java πŸ”—

@@ -50,9 +50,9 @@ public class JoinConferenceDialog extends DialogFragment implements OnBackendCon
 	@Override
 	public Dialog onCreateDialog(Bundle savedInstanceState) {
 		final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-		builder.setTitle(R.string.dialog_title_join_conference);
+		builder.setTitle(R.string.join_public_channel);
 		DialogJoinConferenceBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_join_conference, null, false);
-		DelayedHintHelper.setHint(R.string.conference_address_example, binding.jid);
+		DelayedHintHelper.setHint(R.string.channel_full_jid_example, binding.jid);
 		this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
 		binding.jid.setAdapter(knownHostsAdapter);
 		String prefilledJid = getArguments().getString(PREFILLED_JID_KEY);

src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java πŸ”—

@@ -6,7 +6,6 @@ import android.app.Dialog;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
@@ -14,27 +13,20 @@ import android.databinding.DataBindingUtil;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.support.annotation.DrawableRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
-import android.support.v4.app.ListFragment;
-import android.support.v4.content.ContextCompat;
 import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
 import android.support.v4.widget.SwipeRefreshLayout;
 import android.support.v7.app.ActionBar;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.widget.Toolbar;
 import android.text.Editable;
 import android.text.Html;
-import android.text.SpannableString;
-import android.text.Spanned;
 import android.text.TextWatcher;
 import android.text.method.LinkMovementMethod;
-import android.text.util.Linkify;
 import android.util.Log;
 import android.util.Pair;
 import android.view.ContextMenu;
@@ -56,9 +48,6 @@ import android.widget.Spinner;
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.leinardi.android.speeddial.SpeedDialActionItem;
-import com.leinardi.android.speeddial.SpeedDialView;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -78,12 +67,10 @@ import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
 import eu.siacs.conversations.ui.adapter.ListItemAdapter;
 import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
-import eu.siacs.conversations.ui.service.EmojiService;
 import eu.siacs.conversations.ui.util.JidDialog;
 import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
 import eu.siacs.conversations.ui.util.PendingItem;
 import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
-import eu.siacs.conversations.ui.util.StyledAttributes;
 import eu.siacs.conversations.ui.widget.SwipeRefreshListFragment;
 import eu.siacs.conversations.utils.AccountUtils;
 import eu.siacs.conversations.utils.XmppUri;
@@ -91,7 +78,7 @@ import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import rocks.xmpp.addr.Jid;
 
-public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreateConferenceDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, SwipeRefreshLayout.OnRefreshListener {
+public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreatePrivateGroupChatDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, SwipeRefreshLayout.OnRefreshListener, CreatePublicChannelDialog.CreatePublicChannelDialogListener {
 
 	public static final String EXTRA_INVITE_URI = "eu.siacs.conversations.invite_uri";
 
@@ -216,12 +203,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 			return true;
 		}
 	};
-	private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
-		@Override
-		public void onPageSelected(int position) {
-			onTabChanged();
-		}
-	};
 
 	public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
 		if (accounts.size() > 0) {
@@ -279,35 +260,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 		Toolbar toolbar = (Toolbar) binding.toolbar;
 		setSupportActionBar(toolbar);
 		configureActionBar(getSupportActionBar());
-		this.binding.speedDial.setOnChangeListener(new SpeedDialView.OnChangeListener() {
-			@Override
-			public boolean onMainActionSelected() {
-				if (binding.startConversationViewPager.getCurrentItem() == 0) {
-					String searchString = mSearchEditText != null ? mSearchEditText.getText().toString() : null;
-					if (searchString != null && !searchString.trim().isEmpty()) {
-						try {
-							Jid jid = Jid.of(searchString);
-							if (jid.getLocal() != null && jid.isBareJid() && jid.getDomain().contains(".")) {
-								showCreateContactDialog(jid.toString(), null);
-								return false;
-							}
-						} catch (IllegalArgumentException ignored) {
-							//ignore and fall through
-						}
-					}
-					showCreateContactDialog(null, null);
-				}
-				return false;
-			}
-
-			@Override
-			public void onToggleChanged(boolean isOpen) {
 
-			}
-		});
+		binding.speedDial.inflate(R.menu.start_conversation_fab_submenu);
 
 		binding.tabLayout.setupWithViewPager(binding.startConversationViewPager);
-		binding.startConversationViewPager.addOnPageChangeListener(mOnPageChangeListener);
 		mListPagerAdapter = new ListPagerAdapter(getSupportFragmentManager());
 		binding.startConversationViewPager.setAdapter(mListPagerAdapter);
 
@@ -342,18 +298,40 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 		}
 		mRequestedContactsPermission.set(savedInstanceState != null && savedInstanceState.getBoolean("requested_contacts_permission",false));
 		binding.speedDial.setOnActionSelectedListener(actionItem -> {
+			final String searchString = mSearchEditText != null ? mSearchEditText.getText().toString() : null;
+			final String prefilled;
+			if (isValidJid(searchString)) {
+				prefilled = Jid.ofEscaped(searchString).toEscapedString();
+			} else {
+				prefilled = null;
+			}
 			switch (actionItem.getId()) {
-				case R.id.enter:
-					showJoinConferenceDialog(null);
+				case R.id.join_public_channel:
+					showJoinConferenceDialog(prefilled);
 					break;
-				case R.id.create:
-					showCreateConferenceDialog();
+				case R.id.create_private_group_chat:
+					showCreatePrivateGroupChatDialog();
+					break;
+				case R.id.create_public_channel:
+					showPublicChannelDialog();
+					break;
+				case R.id.create_contact:
+					showCreateContactDialog(prefilled,null);
 					break;
 			}
 			return false;
 		});
 	}
 
+	public static boolean isValidJid(String input) {
+		try {
+			Jid jid = Jid.ofEscaped(input);
+			return !jid.isDomainJid();
+		} catch (IllegalArgumentException e) {
+			return false;
+		}
+	}
+
 	@Override
 	public void onSaveInstanceState(Bundle savedInstanceState) {
 		Intent pendingIntent = pendingViewIntent.peek();
@@ -402,10 +380,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 		switchToConversation(conversation);
 	}
 
-	protected void openConversationForBookmark() {
-		openConversationForBookmark(conference_context_id);
-	}
-
 	protected void openConversationForBookmark(int position) {
 		Bookmark bookmark = (Bookmark) conferences.get(position);
 		openConversationsForBookmark(bookmark);
@@ -504,7 +478,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 		ft.addToBackStack(null);
 		EnterJidDialog dialog = EnterJidDialog.newInstance(
 				mActivatedAccounts,
-				getString(R.string.dialog_title_create_contact),
+				getString(R.string.add_contact),
 				getString(R.string.create),
 				prefilledJid,
 				null,
@@ -554,32 +528,51 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 		joinConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
 	}
 
-	private void showCreateConferenceDialog() {
+	private void showCreatePrivateGroupChatDialog() {
 		FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 		Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
 		if (prev != null) {
 			ft.remove(prev);
 		}
 		ft.addToBackStack(null);
-		CreateConferenceDialog createConferenceFragment = CreateConferenceDialog.newInstance(mActivatedAccounts);
+		CreatePrivateGroupChatDialog createConferenceFragment = CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts);
 		createConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
 	}
 
-	private Account getSelectedAccount(Spinner spinner) {
-		if (!spinner.isEnabled()) {
+	private void showPublicChannelDialog() {
+		FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+		Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
+		if (prev != null) {
+			ft.remove(prev);
+		}
+		ft.addToBackStack(null);
+		CreatePublicChannelDialog dialog = CreatePublicChannelDialog.newInstance(mActivatedAccounts);
+		dialog.show(ft, FRAGMENT_TAG_DIALOG);
+	}
+
+	public static Account getSelectedAccount(Context context, Spinner spinner) {
+		if (spinner == null || !spinner.isEnabled()) {
 			return null;
 		}
-		Jid jid;
-		try {
-			if (Config.DOMAIN_LOCK != null) {
-				jid = Jid.of((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
-			} else {
-				jid = Jid.of((String) spinner.getSelectedItem());
+		if (context instanceof XmppActivity) {
+			Jid jid;
+			try {
+				if (Config.DOMAIN_LOCK != null) {
+					jid = Jid.of((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
+				} else {
+					jid = Jid.of((String) spinner.getSelectedItem());
+				}
+			} catch (final IllegalArgumentException e) {
+				return null;
 			}
-		} catch (final IllegalArgumentException e) {
+			final XmppConnectionService service = ((XmppActivity) context).xmppConnectionService;
+			if (service == null) {
+				return null;
+			}
+			return service.findAccountByJid(jid);
+		} else {
 			return null;
 		}
-		return xmppConnectionService.findAccountByJid(jid);
 	}
 
 	protected void switchToConversation(Contact contact) {
@@ -944,19 +937,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 		mConferenceAdapter.notifyDataSetChanged();
 	}
 
-	private void onTabChanged() {
-		@DrawableRes final int fabDrawable;
-		if (binding.startConversationViewPager.getCurrentItem() == 0) {
-			fabDrawable = R.drawable.ic_person_add_white_24dp;
-			binding.speedDial.clearActionItems();
-		} else {
-			fabDrawable = R.drawable.ic_group_add_white_24dp;
-			binding.speedDial.inflate(R.menu.start_conversation_group_fab);
-		}
-		binding.speedDial.setMainFabClosedDrawable(ContextCompat.getDrawable(this,fabDrawable));
-		invalidateOptionsMenu();
-	}
-
 	@Override
 	public void OnUpdateBlocklist(final Status status) {
 		refreshUi();
@@ -996,7 +976,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 		if (!xmppConnectionServiceBound) {
 			return;
 		}
-		final Account account = getSelectedAccount(spinner);
+		final Account account = getSelectedAccount(this, spinner);
 		if (account == null) {
 			return;
 		}
@@ -1014,7 +994,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 		if (!xmppConnectionServiceBound) {
 			return;
 		}
-		final Account account = getSelectedAccount(spinner);
+		final Account account = getSelectedAccount(this, spinner);
 		if (account == null) {
 			return;
 		}
@@ -1073,6 +1053,35 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 		}
 	}
 
+	@Override
+	public void onCreatePublicChannel(Account account, String name, Jid address) {
+		mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG);
+		mToast.show();
+		xmppConnectionService.createPublicChannel(account, name, address, new UiCallback<Conversation>() {
+			@Override
+			public void success(Conversation conversation) {
+				runOnUiThread(() -> {
+					hideToast();
+					switchToConversation(conversation);
+				});
+
+			}
+
+			@Override
+			public void error(int errorCode, Conversation conversation) {
+				runOnUiThread(() -> {
+					replaceToast(getString(errorCode));
+					switchToConversation(conversation);
+				});
+			}
+
+			@Override
+			public void userInputRequried(PendingIntent pi, Conversation object) {
+
+			}
+		});
+	}
+
 	public static class MyListFragment extends SwipeRefreshListFragment {
 		private AdapterView.OnItemClickListener mOnItemClickListener;
 		private int mResContextMenu;
@@ -1154,9 +1163,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 				case R.id.context_delete_contact:
 					activity.deleteContact();
 					break;
-				case R.id.context_join_conference:
-					activity.openConversationForBookmark();
-					break;
 				case R.id.context_share_uri:
 					activity.shareBookmarkUri();
 					break;
@@ -1221,7 +1227,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
 				case 0:
 					return getResources().getString(R.string.contacts);
 				case 1:
-					return getResources().getString(R.string.conferences);
+					return getResources().getString(R.string.bookmarks);
 				default:
 					return super.getPageTitle(position);
 			}

src/main/java/eu/siacs/conversations/utils/CryptoHelper.java πŸ”—

@@ -63,7 +63,8 @@ public final class CryptoHelper {
     }
 
     public static String pronounceable(SecureRandom random) {
-        char[] output = new char[random.nextInt(4) * 2 + 5];
+        final int rand = random.nextInt(4);
+        char[] output = new char[rand * 2 + (5 - rand)];
         boolean vowel = random.nextBoolean();
         for (int i = 0; i < output.length; ++i) {
             output[i] = vowel ? VOWELS[random.nextInt(VOWELS.length)] : CONSONANTS[random.nextInt(CONSONANTS.length)];

src/main/res/layout/activity_start_conversation.xml πŸ”—

@@ -46,10 +46,10 @@
             app:backgroundTint="?colorPrimary"
             android:layout_alignParentRight="true"
             android:layout_alignParentBottom="true"
-            app:sdMainFabClosedSrc="@drawable/ic_person_add_white_24dp"
+            app:sdMainFabClosedSrc="@drawable/ic_add_white_24dp"
             app:sdMainFabClosedBackgroundColor="?colorPrimary"
-            app:sdMainFabOpenedSrc="@drawable/ic_close_white_24dp"
             app:sdMainFabOpenedBackgroundColor="?colorPrimaryDark"
+            app:sdUseReverseAnimationOnClose="true"
             app:sdOverlayLayout="@id/overlay"/>
     </RelativeLayout>
 </layout>

src/main/res/layout/create_conference_dialog.xml πŸ”—

@@ -34,7 +34,7 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:hint="@string/create_dialog_group_chat_name"
-                android:imeOptions="actionNext"/>
+                android:imeOptions="actionNext|flagNoExtractUi"/>
         </android.support.design.widget.TextInputLayout>
     </LinearLayout>
 </layout>

src/main/res/layout/create_public_channel_dialog.xml πŸ”—

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:padding="?dialogPreferredPadding">
+
+        <TextView
+            style="@style/InputLabel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/your_account"/>
+
+        <Spinner
+            android:id="@+id/account"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"/>
+
+        <android.support.design.widget.TextInputLayout
+            android:id="@+id/name_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
+            app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
+
+            <eu.siacs.conversations.ui.widget.EmojiWrapperEditText
+                android:id="@+id/group_chat_name"
+                style="@style/Widget.Conversations.EditText"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:hint="@string/create_dialog_channel_name"
+                android:imeOptions="actionNext|flagNoExtractUi"/>
+        </android.support.design.widget.TextInputLayout>
+        <android.support.design.widget.TextInputLayout
+            android:id="@+id/xmpp_address_layout"
+            android:visibility="gone"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:hint="@string/xmpp_address"
+            app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
+            app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
+
+            <AutoCompleteTextView
+                android:id="@+id/jid"
+                style="@style/Widget.Conversations.EditText"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:inputType="textEmailAddress"
+                android:imeOptions="actionDone|flagNoExtractUi"/>
+        </android.support.design.widget.TextInputLayout>
+    </LinearLayout>
+</layout>

src/main/res/layout/dialog_join_conference.xml πŸ”—

@@ -26,7 +26,7 @@
             android:id="@+id/account_jid_layout"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:hint="@string/conference_address"
+            android:hint="@string/xmpp_address"
             app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
             app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
 
@@ -36,7 +36,7 @@
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:inputType="textEmailAddress"
-                android:imeOptions="actionDone"/>
+                android:imeOptions="actionDone|flagNoExtractUi"/>
         </android.support.design.widget.TextInputLayout>
 
         <CheckBox

src/main/res/layout/enter_jid_dialog.xml πŸ”—

@@ -36,7 +36,7 @@
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:inputType="textEmailAddress"
-                android:imeOptions="actionDone"/>
+                android:imeOptions="actionDone|flagNoExtractUi"/>
         </android.support.design.widget.TextInputLayout>
     </LinearLayout>
 </layout>

src/main/res/menu/conference_context.xml πŸ”—

@@ -1,9 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android" >
 
-    <item
-        android:id="@+id/context_join_conference"
-        android:title="@string/join_conference"/>
     <item
         android:id="@+id/context_delete_conference"
         android:title="@string/delete_bookmark"/>

src/main/res/menu/start_conversation_fab_submenu.xml πŸ”—

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/join_public_channel"
+        android:title="@string/join_public_channel"
+        android:icon="@drawable/ic_input_white_24dp"/>
+    <item
+        android:id="@+id/create_public_channel"
+        android:title="@string/create_public_channel"
+        android:icon="@drawable/ic_public_white_24dp"/>
+    <item
+        android:id="@+id/create_private_group_chat"
+        android:title="@string/create_private_group_chat"
+        android:icon="@drawable/ic_group_white_24dp"/>
+    <item
+        android:id="@+id/create_contact"
+        android:title="@string/add_contact"
+        android:icon="@drawable/ic_person_white_48dp"/>
+</menu>

src/main/res/menu/start_conversation_group_fab.xml πŸ”—

@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:id="@+id/enter"
-        android:title="@string/enter_jabber_id"
-        android:icon="@drawable/ic_input_white_24dp"/>
-    <item
-        android:id="@+id/create"
-        android:title="@string/create_group_chat"
-        android:icon="@drawable/ic_edit_white_24dp"/>
-</menu>

src/main/res/values/strings.xml πŸ”—

@@ -7,6 +7,7 @@
     <string name="action_end_conversation">Close this conversation</string>
     <string name="action_contact_details">Contact details</string>
     <string name="action_muc_details">Group chat details</string>
+    <string name="channel_details">Channel details</string>
     <string name="action_secure">Secure conversation</string>
     <string name="action_add_account">Add account</string>
     <string name="action_edit_contact">Edit name</string>
@@ -211,12 +212,9 @@
     <string name="fetching_keys">Fetching keys…</string>
     <string name="done">Done</string>
     <string name="decrypt">Decrypt</string>
-    <string name="conferences">Group chats</string>
+    <string name="bookmarks">Bookmarks</string>
     <string name="search">Search</string>
-    <string name="dialog_title_create_contact">Create Contact</string>
     <string name="enter_contact">Enter Contact</string>
-    <string name="join_conference">Join group chat</string>
-    <string name="dialog_title_join_conference">Join Group Chat</string>
     <string name="delete_contact">Delete contact</string>
     <string name="view_contact_details">View contact details</string>
     <string name="block_contact">Block contact</string>
@@ -225,8 +223,8 @@
     <string name="select">Select</string>
     <string name="contact_already_exists">The contact already exists</string>
     <string name="join">Join</string>
-    <string name="conference_address">Group chat address</string>
-    <string name="conference_address_example">room@conference.example.com/nick</string>
+    <string name="channel_full_jid_example">channel@conference.example.com/nick</string>
+    <string name="channel_bare_jid_example">channel@conference.example.com</string>
     <string name="save_as_bookmark">Save as bookmark</string>
     <string name="delete_bookmark">Delete bookmark</string>
     <string name="destroy_room">Destroy group chat</string>
@@ -416,7 +414,8 @@
     <string name="no_application_found_to_display_location">No application found to display location</string>
     <string name="location">Location</string>
     <string name="title_undo_swipe_out_conversation">Conversation closed</string>
-    <string name="title_undo_swipe_out_muc">Left group chat</string>
+    <string name="title_undo_swipe_out_group_chat">Left private group chat</string>
+    <string name="title_undo_swipe_out_channel">Left public channel</string>
     <string name="pref_dont_trust_system_cas_title">Don’t trust system CAs</string>
     <string name="pref_dont_trust_system_cas_summary">All certificates must be manually approved</string>
     <string name="pref_remove_trusted_certificates_title">Remove certificates</string>
@@ -541,7 +540,6 @@
     <string name="device_does_not_support_battery_op">Your device does not support opting out of battery optimization</string>
     <string name="registration_please_wait">Registration failed: Try again later</string>
     <string name="registration_password_too_weak">Registration failed: Password too weak</string>
-    <string name="dialog_title_create_conference">Create Group Chat</string>
     <string name="choose_participants">Choose participants</string>
     <string name="creating_conference">Creating group chat…</string>
     <string name="invite_again">Invite again</string>
@@ -824,4 +822,16 @@
     <string name="backup_channel_name">Backup &amp; Restore</string>
     <string name="enter_jabber_id">Enter Jabber ID</string>
     <string name="create_group_chat">Create group chat</string>
+    <string name="join_public_channel">Join public channel</string>
+    <string name="create_private_group_chat">Create private group chat</string>
+    <string name="create_public_channel">Create public channel</string>
+    <string name="create_dialog_channel_name">Channel name</string>
+    <string name="xmpp_address">XMPP address</string>
+    <string name="please_enter_name">Please provide a name for the channel</string>
+    <string name="please_enter_xmpp_address">Please provide an XMPP address</string>
+    <string name="this_is_an_xmpp_address">This is an XMPP address. Please provide a name.</string>
+    <string name="creating_channel">Creating public channel…</string>
+    <string name="channel_already_exists">This channel already exits</string>
+    <string name="joined_an_existing_channel">You’ve joined an existing channel</string>
+    <string name="unable_to_set_channel_configuration">Unable to set channel configuration</string>
 </resources>

src/main/res/values/themes.xml πŸ”—

@@ -10,7 +10,7 @@
         <item name="color_background_primary">@color/grey50</item>
         <item name="color_background_secondary">@color/grey200</item>
         <item name="color_background_tertiary">@color/grey300</item>
-        <item name="color_background_overlay">@color/grey300_40</item>
+        <item name="color_background_overlay">@color/black12</item>
         <item name="color_warning">@color/red_a700</item>
         <item name="TextColorOnline">@color/green600</item>
         <item name="TextColorError">@color/red800</item>
@@ -123,7 +123,7 @@
         <item name="color_background_primary">@color/grey800</item>
         <item name="color_background_secondary">@color/grey900</item>
         <item name="color_background_tertiary">@color/grey700</item>
-        <item name="color_background_overlay">@color/grey700_40</item>
+        <item name="color_background_overlay">@color/black26</item>
         <item name="activity_background_search">@drawable/search_background_dark</item>
         <item name="activity_background_no_results">@drawable/no_results_background_dark</item>
         <item name="list_item_background">@drawable/list_item_background_dark</item>