From 1b3406cdbb6c620cca975b0bc32b43b60fa246cb Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Thu, 7 Feb 2019 21:11:50 +0100 Subject: provide the same fab submenu for both tabs. rename tab to bookmark --- .../de/pixart/messenger/generator/IqGenerator.java | 15 +- .../de/pixart/messenger/parser/PresenceParser.java | 2 +- .../messenger/services/XmppConnectionService.java | 33 ++- .../messenger/ui/ConferenceDetailsActivity.java | 24 +- .../pixart/messenger/ui/ConversationFragment.java | 2 +- .../messenger/ui/CreateConferenceDialog.java | 93 ------- .../messenger/ui/CreatePrivateGroupChatDialog.java | 93 +++++++ .../messenger/ui/CreatePublicChannelDialog.java | 296 +++++++++++++++++++++ .../pixart/messenger/ui/JoinConferenceDialog.java | 4 +- .../messenger/ui/StartConversationActivity.java | 168 ++++++------ .../de/pixart/messenger/utils/CryptoHelper.java | 3 +- 11 files changed, 531 insertions(+), 202 deletions(-) delete mode 100644 src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java create mode 100644 src/main/java/de/pixart/messenger/ui/CreatePrivateGroupChatDialog.java create mode 100644 src/main/java/de/pixart/messenger/ui/CreatePublicChannelDialog.java (limited to 'src/main/java') diff --git a/src/main/java/de/pixart/messenger/generator/IqGenerator.java b/src/main/java/de/pixart/messenger/generator/IqGenerator.java index b2f43cbcf..d7ac6f994 100644 --- a/src/main/java/de/pixart/messenger/generator/IqGenerator.java +++ b/src/main/java/de/pixart/messenger/generator/IqGenerator.java @@ -466,7 +466,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"); @@ -478,6 +478,19 @@ 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); } diff --git a/src/main/java/de/pixart/messenger/parser/PresenceParser.java b/src/main/java/de/pixart/messenger/parser/PresenceParser.java index 0e0b67382..8728a8a9a 100644 --- a/src/main/java/de/pixart/messenger/parser/PresenceParser.java +++ b/src/main/java/de/pixart/messenger/parser/PresenceParser.java @@ -95,7 +95,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) { diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index e231b2f6c..41b83a76d 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -96,7 +96,6 @@ import de.pixart.messenger.entities.Bookmark; import de.pixart.messenger.entities.Contact; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.Conversational; -import de.pixart.messenger.entities.DownloadableFile; import de.pixart.messenger.entities.Message; import de.pixart.messenger.entities.MucOptions; import de.pixart.messenger.entities.MucOptions.OnRenameListener; @@ -104,8 +103,6 @@ import de.pixart.messenger.entities.Presence; import de.pixart.messenger.entities.PresenceTemplate; import de.pixart.messenger.entities.Roster; import de.pixart.messenger.entities.ServiceDiscoveryResult; -import de.pixart.messenger.entities.Transferable; -import de.pixart.messenger.entities.TransferablePlaceholder; import de.pixart.messenger.generator.AbstractGenerator; import de.pixart.messenger.generator.IqGenerator; import de.pixart.messenger.generator.MessageGenerator; @@ -2673,6 +2670,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; } final Jid joinJid = mucOptions.getSelf().getFullJid(); @@ -2956,6 +2956,31 @@ public class XmppConnectionService extends Service { return null; } + public void createPublicChannel(final Account account, final String name, final Jid address, final UiCallback 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 jids, @@ -2975,7 +3000,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); } diff --git a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java index 02de197da..76c026308 100644 --- a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java @@ -3,14 +3,8 @@ package de.pixart.messenger.ui; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.IntentSender.SendIntentException; -import android.content.res.Resources; import android.databinding.DataBindingUtil; -import android.graphics.Bitmap; import android.graphics.PorterDuff; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; @@ -18,38 +12,24 @@ import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.util.Log; -import android.view.ContextMenu; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.view.WindowManager; -import android.widget.CompoundButton; -import android.widget.ImageView; import android.widget.Toast; -import org.openintents.openpgp.util.OpenPgpUtils; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; import de.pixart.messenger.Config; import de.pixart.messenger.R; -import de.pixart.messenger.crypto.PgpEngine; import de.pixart.messenger.databinding.ActivityMucDetailsBinding; -import de.pixart.messenger.databinding.ContactBinding; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Bookmark; -import de.pixart.messenger.entities.Contact; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.MucOptions; import de.pixart.messenger.entities.MucOptions.User; -import de.pixart.messenger.services.EmojiService; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.services.XmppConnectionService.OnConversationUpdate; import de.pixart.messenger.services.XmppConnectionService.OnMucRosterUpdate; @@ -68,13 +48,11 @@ import de.pixart.messenger.utils.MenuDoubleTabUtil; import de.pixart.messenger.utils.StringUtils; import de.pixart.messenger.utils.StylingHelper; import de.pixart.messenger.utils.TimeframeUtils; -import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.utils.XmppUri; import me.drakeet.support.toast.ToastCompat; import rocks.xmpp.addr.Jid; import static de.pixart.messenger.entities.Bookmark.printableValue; -import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; import static de.pixart.messenger.utils.StringUtils.changed; public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnConfigurationPushed, TextWatcher, OnMediaLoaded { @@ -520,7 +498,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } else { account = mConversation.getAccount().getJid().asBareJid().toString(); } - + setTitle(mucOptions.isPrivateAndNonAnonymous() ? R.string.conference_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()); diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index 56c989c8a..279dcb659 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -1131,7 +1131,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (conversation != null) { if (conversation.getMode() == Conversation.MODE_MULTI) { - menuInviteContact.setVisible(true); + menuInviteContact.setVisible(conversation.getMucOptions().canInvite()); menuArchiveChat.setTitle(R.string.action_end_conversation_muc); } else { menuInviteContact.setVisible(false); diff --git a/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java b/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java deleted file mode 100644 index 059a6b94c..000000000 --- a/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java +++ /dev/null @@ -1,93 +0,0 @@ -package de.pixart.messenger.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.support.v7.app.AlertDialog; -import android.view.View; -import android.widget.Spinner; - -import java.util.ArrayList; -import java.util.List; - -import de.pixart.messenger.R; -import de.pixart.messenger.databinding.CreateConferenceDialogBinding; -import de.pixart.messenger.services.XmppConnectionService; -import de.pixart.messenger.ui.util.DelayedHintHelper; - - -public class CreateConferenceDialog extends DialogFragment { - - private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list"; - private static final String MULTIPLE_ACCOUNTS = "multiple_accounts_enabled"; - public XmppConnectionService xmppConnectionService; - private CreateConferenceDialogListener mListener; - - public static CreateConferenceDialog newInstance(List accounts, boolean multipleAccounts) { - CreateConferenceDialog dialog = new CreateConferenceDialog(); - Bundle bundle = new Bundle(); - bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList) accounts); - bundle.putBoolean(MULTIPLE_ACCOUNTS, multipleAccounts); - dialog.setArguments(bundle); - return dialog; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setRetainInstance(true); - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle(R.string.create_conference); - CreateConferenceDialogBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.create_conference_dialog, null, false); - if (getArguments().getBoolean(MULTIPLE_ACCOUNTS)) { - binding.yourAccount.setVisibility(View.VISIBLE); - binding.account.setVisibility(View.VISIBLE); - } else { - binding.yourAccount.setVisibility(View.GONE); - binding.account.setVisibility(View.GONE); - } - ArrayList mActivatedAccounts = getArguments().getStringArrayList(ACCOUNTS_LIST_KEY); - StartConversationActivity.populateAccountSpinner(getActivity(), mActivatedAccounts, binding.account); - builder.setView(binding.getRoot()); - builder.setPositiveButton(R.string.choose_participants, (dialog, which) -> mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim())); - builder.setNegativeButton(R.string.cancel, null); - DelayedHintHelper.setHint(R.string.providing_a_name_is_optional, binding.groupChatName); - binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> { - mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim()); - return true; - }); - return builder.create(); - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - try { - mListener = (CreateConferenceDialogListener) context; - } catch (ClassCastException e) { - throw new ClassCastException(context.toString() - + " must implement CreateConferenceDialogListener"); - } - } - - @Override - public void onDestroyView() { - Dialog dialog = getDialog(); - if (dialog != null && getRetainInstance()) { - dialog.setDismissMessage(null); - } - super.onDestroyView(); - } - - public interface CreateConferenceDialogListener { - void onCreateDialogPositiveClick(Spinner spinner, String subject); - } -} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/CreatePrivateGroupChatDialog.java b/src/main/java/de/pixart/messenger/ui/CreatePrivateGroupChatDialog.java new file mode 100644 index 000000000..c77211e9d --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/CreatePrivateGroupChatDialog.java @@ -0,0 +1,93 @@ +package de.pixart.messenger.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.support.v7.app.AlertDialog; +import android.view.View; +import android.widget.Spinner; + +import java.util.ArrayList; +import java.util.List; + +import de.pixart.messenger.R; +import de.pixart.messenger.databinding.CreateConferenceDialogBinding; +import de.pixart.messenger.services.XmppConnectionService; +import de.pixart.messenger.ui.util.DelayedHintHelper; + + +public class CreatePrivateGroupChatDialog extends DialogFragment { + + private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list"; + private static final String MULTIPLE_ACCOUNTS = "multiple_accounts_enabled"; + public XmppConnectionService xmppConnectionService; + private CreateConferenceDialogListener mListener; + + public static CreatePrivateGroupChatDialog newInstance(List accounts, boolean multipleAccounts) { + CreatePrivateGroupChatDialog dialog = new CreatePrivateGroupChatDialog(); + Bundle bundle = new Bundle(); + bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList) accounts); + bundle.putBoolean(MULTIPLE_ACCOUNTS, multipleAccounts); + dialog.setArguments(bundle); + return dialog; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setRetainInstance(true); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.create_private_group_chat); + CreateConferenceDialogBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.create_conference_dialog, null, false); + if (getArguments().getBoolean(MULTIPLE_ACCOUNTS)) { + binding.yourAccount.setVisibility(View.VISIBLE); + binding.account.setVisibility(View.VISIBLE); + } else { + binding.yourAccount.setVisibility(View.GONE); + binding.account.setVisibility(View.GONE); + } + ArrayList mActivatedAccounts = getArguments().getStringArrayList(ACCOUNTS_LIST_KEY); + StartConversationActivity.populateAccountSpinner(getActivity(), mActivatedAccounts, binding.account); + builder.setView(binding.getRoot()); + builder.setPositiveButton(R.string.choose_participants, (dialog, which) -> mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim())); + builder.setNegativeButton(R.string.cancel, null); + DelayedHintHelper.setHint(R.string.providing_a_name_is_optional, binding.groupChatName); + binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> { + mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim()); + return true; + }); + return builder.create(); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + mListener = (CreateConferenceDialogListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement CreateConferenceDialogListener"); + } + } + + @Override + public void onDestroyView() { + Dialog dialog = getDialog(); + if (dialog != null && getRetainInstance()) { + dialog.setDismissMessage(null); + } + super.onDestroyView(); + } + + public interface CreateConferenceDialogListener { + void onCreateDialogPositiveClick(Spinner spinner, String subject); + } +} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/CreatePublicChannelDialog.java b/src/main/java/de/pixart/messenger/ui/CreatePublicChannelDialog.java new file mode 100644 index 000000000..fe3244483 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/CreatePublicChannelDialog.java @@ -0,0 +1,296 @@ +package de.pixart.messenger.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 de.pixart.messenger.R; +import de.pixart.messenger.databinding.CreatePublicChannelDialogBinding; +import de.pixart.messenger.entities.Account; +import de.pixart.messenger.services.XmppConnectionService; +import de.pixart.messenger.ui.adapter.KnownHostsAdapter; +import de.pixart.messenger.ui.interfaces.OnBackendConnected; +import de.pixart.messenger.ui.util.DelayedHintHelper; +import de.pixart.messenger.utils.CryptoHelper; +import de.pixart.messenger.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 accounts) { + CreatePublicChannelDialog dialog = new CreatePublicChannelDialog(); + Bundle bundle = new Bundle(); + bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList) 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 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 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(); + } +} \ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java b/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java index 4d05d6d2c..67cc3a9d2 100644 --- a/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java +++ b/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java @@ -54,9 +54,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.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); diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java index 13a7c9e80..97e1f9fd3 100644 --- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java @@ -66,7 +66,6 @@ import de.pixart.messenger.entities.Contact; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.entities.Presence; -import de.pixart.messenger.services.EmojiService; import de.pixart.messenger.services.QuickConversationsService; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate; @@ -81,9 +80,7 @@ import de.pixart.messenger.xmpp.OnUpdateBlocklist; import de.pixart.messenger.xmpp.XmppConnection; import rocks.xmpp.addr.Jid; -import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; - -public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreateConferenceDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener { +public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreatePrivateGroupChatDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, CreatePublicChannelDialog.CreatePublicChannelDialogListener { public static final String EXTRA_INVITE_URI = "de.pixart.messenger.invite_uri"; @@ -208,12 +205,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 accounts, Spinner spinner) { if (accounts.size() > 0) { @@ -271,33 +262,8 @@ 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); @@ -331,18 +297,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(); @@ -390,10 +378,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); @@ -499,7 +483,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne ft.addToBackStack(null); EnterJidDialog dialog = EnterJidDialog.newInstance( mActivatedAccounts, - getString(R.string.create_contact), + getString(R.string.add_contact), getString(R.string.create), prefilledJid, null, @@ -551,32 +535,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, xmppConnectionService.multipleAccounts()); + CreatePrivateGroupChatDialog createConferenceFragment = CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts, xmppConnectionService.multipleAccounts()); 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) { @@ -919,19 +922,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(); @@ -968,7 +958,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; } @@ -987,7 +977,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; } @@ -1030,6 +1020,35 @@ public class StartConversationActivity extends XmppActivity implements XmppConne refreshUi(); } + @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() { + @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 ListFragment { private AdapterView.OnItemClickListener mOnItemClickListener; private int mResContextMenu; @@ -1111,9 +1130,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; @@ -1178,7 +1194,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); } diff --git a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java index 2104349d8..5d1e7980c 100644 --- a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java +++ b/src/main/java/de/pixart/messenger/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)]; -- cgit v1.2.3