diff options
author | Daniel Gultsch <daniel@gultsch.de> | 2020-12-01 22:31:30 +0300 |
---|---|---|
committer | Daniel Gultsch <daniel@gultsch.de> | 2020-12-01 22:31:30 +0300 |
commit | 1f392a688d1439fdf163cc767e3630d566726d83 (patch) | |
tree | e78f1903e43e3590ef6cd20db989cac21a3068cd /src | |
parent | 92083fec83d6dda988ae98f012308db62fe7bcdc (diff) |
initial (untested) support for easy onboarding invites
Diffstat (limited to 'src')
14 files changed, 483 insertions, 1 deletions
diff --git a/src/conversations/AndroidManifest.xml b/src/conversations/AndroidManifest.xml index 90b78ed4c..62396bed1 100644 --- a/src/conversations/AndroidManifest.xml +++ b/src/conversations/AndroidManifest.xml @@ -21,6 +21,10 @@ android:label="@string/create_new_account" android:launchMode="singleTask" /> <activity + android:name=".ui.EasyOnboardingInviteActivity" + android:label="@string/invite_to_app" + android:launchMode="singleTask" /> + <activity android:name=".ui.ImportBackupActivity" android:label="@string/restore_backup" android:launchMode="singleTask"> diff --git a/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java new file mode 100644 index 000000000..840fd6153 --- /dev/null +++ b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java @@ -0,0 +1,157 @@ +package eu.siacs.conversations.ui; + +import android.app.Activity; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.os.Bundle; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import com.google.common.base.Strings; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.databinding.ActivityEasyInviteBinding; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.BarcodeProvider; +import eu.siacs.conversations.utils.EasyOnboardingInvite; +import eu.siacs.conversations.xmpp.Jid; + +public class EasyOnboardingInviteActivity extends XmppActivity implements EasyOnboardingInvite.OnInviteRequested { + + private ActivityEasyInviteBinding binding; + + private EasyOnboardingInvite easyOnboardingInvite; + + + @Override + public void onCreate(final Bundle bundle) { + super.onCreate(bundle); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_easy_invite); + setSupportActionBar((Toolbar) binding.toolbar); + configureActionBar(getSupportActionBar(), true); + this.binding.shareButton.setOnClickListener(v -> share()); + if (bundle != null && bundle.containsKey("invite")) { + this.easyOnboardingInvite = bundle.getParcelable("invite"); + if (this.easyOnboardingInvite != null) { + showInvite(this.easyOnboardingInvite); + return; + } + } + this.showLoading(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.easy_onboarding_invite, menu); + final MenuItem share = menu.findItem(R.id.action_share); + share.setVisible(easyOnboardingInvite != null); + return super.onCreateOptionsMenu(menu); + } + + public boolean onOptionsItemSelected(MenuItem menuItem) { + if (menuItem.getItemId() == R.id.action_share) { + share(); + return true; + } else { + return super.onOptionsItemSelected(menuItem); + } + } + + private void share() { + final String shareText = getString( + R.string.easy_invite_share_text, + easyOnboardingInvite.getDomain(), + easyOnboardingInvite.getLandingUrl() + ); + final Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, shareText); + sendIntent.setType("text/plain"); + startActivity(Intent.createChooser(sendIntent, getString(R.string.share_invite_with))); + } + + @Override + protected void refreshUiReal() { + invalidateOptionsMenu(); + if (easyOnboardingInvite != null) { + showInvite(easyOnboardingInvite); + } else { + showLoading(); + } + } + + private void showLoading() { + this.binding.inProgress.setVisibility(View.VISIBLE); + this.binding.invite.setVisibility(View.GONE); + } + + private void showInvite(final EasyOnboardingInvite invite) { + this.binding.inProgress.setVisibility(View.GONE); + this.binding.invite.setVisibility(View.VISIBLE); + this.binding.tapToShare.setText(getString(R.string.tap_share_button_send_invite, invite.getDomain())); + final Point size = new Point(); + getWindowManager().getDefaultDisplay().getSize(size); + final int width = Math.min(size.x, size.y); + final String content; + if (Strings.isNullOrEmpty(invite.getLandingUrl())) { + content = invite.getUri(); + } else { + content = invite.getLandingUrl(); + } + final Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(content, width); + binding.qrCode.setImageBitmap(bitmap); + } + + @Override + public void onSaveInstanceState(Bundle bundle) { + super.onSaveInstanceState(bundle); + if (easyOnboardingInvite != null) { + bundle.putParcelable("invite", easyOnboardingInvite); + } + } + + @Override + void onBackendConnected() { + if (easyOnboardingInvite != null) { + return; + } + final Intent launchIntent = getIntent(); + final String accountExtra = launchIntent.getStringExtra(EXTRA_ACCOUNT); + final Jid jid = accountExtra == null ? null : Jid.ofEscaped(accountExtra); + if (jid == null) { + return; + } + final Account account = xmppConnectionService.findAccountByJid(jid); + xmppConnectionService.requestEasyOnboardingInvite(account, this); + } + + public static void launch(final Account account, final Activity context) { + final Intent intent = new Intent(context, EasyOnboardingInviteActivity.class); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + context.startActivity(intent); + } + + @Override + public void inviteRequested(EasyOnboardingInvite invite) { + this.easyOnboardingInvite = invite; + Log.d(Config.LOGTAG, "invite requested"); + refreshUi(); + } + + @Override + public void inviteRequestFailed(final String message) { + runOnUiThread(() -> { + if (!Strings.isNullOrEmpty(message)) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + } + finish(); + }); + } +} diff --git a/src/conversations/res/layout/activity_easy_invite.xml b/src/conversations/res/layout/activity_easy_invite.xml new file mode 100644 index 000000000..8bbf11c03 --- /dev/null +++ b/src/conversations/res/layout/activity_easy_invite.xml @@ -0,0 +1,83 @@ +<?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="fill_parent" + android:layout_height="fill_parent" + android:background="?attr/color_background_primary" + android:orientation="vertical"> + + <include + android:id="@+id/toolbar" + layout="@layout/toolbar" /> + + <LinearLayout + android:id="@+id/in_progress" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:visibility="gone"> + + <ProgressBar + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> + </LinearLayout> + + <RelativeLayout + android:id="@+id/invite" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" + android:visibility="visible"> + + <TextView + android:id="@+id/tap_to_share" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/tap_share_button_send_invite" + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + + <TextView + android:id="@+id/scan_the_code" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/tap_to_share" + android:layout_marginTop="24sp" + android:text="@string/if_contact_is_nearby_use_qr" + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + + <ImageView + android:id="@+id/qr_code" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_above="@+id/share_button" + android:layout_below="@id/scan_the_code" + android:layout_alignParentStart="true" + android:layout_alignParentRight="true" + android:layout_centerHorizontal="true" + android:layout_margin="24sp" + android:scaleType="fitCenter" /> + + <Button + android:id="@+id/share_button" + style="@style/Widget.Conversations.Button.Borderless" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:minWidth="0dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:text="@string/share" + android:layout_centerHorizontal="true" + android:textColor="?attr/colorAccent" /> + + </RelativeLayout> + + </LinearLayout> +</layout>
\ No newline at end of file diff --git a/src/conversations/res/menu/easy_onboarding_invite.xml b/src/conversations/res/menu/easy_onboarding_invite.xml new file mode 100644 index 000000000..0e086b515 --- /dev/null +++ b/src/conversations/res/menu/easy_onboarding_invite.xml @@ -0,0 +1,10 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + <item + android:id="@+id/action_share" + android:icon="?attr/icon_share" + android:title="@string/invite" + app:showAsAction="always" /> +</menu>
\ No newline at end of file diff --git a/src/conversations/res/values/strings.xml b/src/conversations/res/values/strings.xml index a5e80e9f6..f9aaec9ee 100644 --- a/src/conversations/res/values/strings.xml +++ b/src/conversations/res/values/strings.xml @@ -9,4 +9,8 @@ <string name="magic_create_text_fixed">You have been invited to %1$s. A username has already been picked for you. We will guide you through the process of creating an account.\nYou will be able to communicate with users of other providers by giving them your full XMPP address.</string> <string name="your_server_invitation">Your server invitation</string> <string name="improperly_formatted_provisioning">Improperly formatted provisioning code</string> + <string name="tap_share_button_send_invite">Tap the share button to send your contact an invitation to %1$s.</string> + <string name="if_contact_is_nearby_use_qr">If your contact is nearby, they can also scan the code below to accept your invitation.</string> + <string name="easy_invite_share_text">Join %1$s and chat with me: %2$s</string> + <string name="share_invite_with">Share invite with…</string> </resources>
\ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 3225be67d..9adef0309 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -120,6 +120,7 @@ import eu.siacs.conversations.ui.interfaces.OnSearchResultsAvailable; import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.ConversationsFileObserver; import eu.siacs.conversations.utils.CryptoHelper; +import eu.siacs.conversations.utils.EasyOnboardingInvite; import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.PhoneHelper; @@ -1619,6 +1620,43 @@ public class XmppConnectionService extends Service { sendMessage(message, true, delay); } + public void requestEasyOnboardingInvite(final Account account, final EasyOnboardingInvite.OnInviteRequested callback) { + final XmppConnection connection = account.getXmppConnection(); + final Jid jid = connection == null ? null : connection.getJidForCommand(Namespace.EASY_ONBOARDING_INVITE); + if (jid == null) { + callback.inviteRequestFailed(getString(R.string.server_does_not_support_easy_onboarding_invites)); + return; + } + final IqPacket request = new IqPacket(IqPacket.TYPE.SET); + request.setTo(jid); + final Element command = request.addChild("command", Namespace.COMMANDS); + command.setAttribute("node", Namespace.COMMANDS); + command.setAttribute("action", "execute"); + sendIqPacket(account, request, (a, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + final Element resultCommand = response.findChild("command", Namespace.COMMANDS); + final Element x = resultCommand == null ? null : resultCommand.findChild("x", Namespace.DATA); + if (x != null) { + final Data data = Data.parse(x); + final String uri = data.getValue("uri"); + final String landingUrl = data.getValue("landing-url"); + if (uri != null) { + final EasyOnboardingInvite invite = new EasyOnboardingInvite(jid.getDomain().toEscapedString(), uri, landingUrl); + callback.inviteRequested(invite); + return; + } + } + callback.inviteRequestFailed(getString(R.string.unable_to_parse_invite)); + Log.d(Config.LOGTAG, response.toString()); + } else if (response.getType() == IqPacket.TYPE.ERROR) { + callback.inviteRequestFailed(IqParser.extractErrorMessage(response)); + } else { + callback.inviteRequestFailed(getString(R.string.remote_server_timeout)); + } + }); + + } + public void fetchRosterFromServer(final Account account) { final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET); if (!"".equals(account.getRosterVersion())) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index f0c3bc181..07e35a50e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -2117,6 +2117,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke this.binding.textinput.setKeyboardListener(this); messageListAdapter.updatePreferences(); refresh(false); + activity.invalidateOptionsMenu(); this.conversation.messagesLoaded.set(true); Log.d(Config.LOGTAG, "scrolledToBottomAndNoPending=" + Boolean.toString(scrolledToBottomAndNoPending)); @@ -2397,7 +2398,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } updateSendButton(); updateEditablity(); - activity.invalidateOptionsMenu(); } } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 049c0f27e..026a0cafa 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -131,6 +131,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override protected void refreshUiReal() { + invalidateOptionsMenu(); for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) { refreshFragment(id); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java index b76caaec7..ae81874e1 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java @@ -30,6 +30,7 @@ package eu.siacs.conversations.ui; import android.app.Activity; +import android.app.AlertDialog; import android.app.Fragment; import android.content.Intent; import android.databinding.DataBindingUtil; @@ -48,12 +49,16 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import com.google.common.collect.Collections2; + import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.FragmentConversationsOverviewBinding; +import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.ui.adapter.ConversationAdapter; @@ -65,6 +70,7 @@ import eu.siacs.conversations.ui.util.PendingItem; import eu.siacs.conversations.ui.util.ScrollState; import eu.siacs.conversations.ui.util.StyledAttributes; import eu.siacs.conversations.utils.AccountUtils; +import eu.siacs.conversations.utils.EasyOnboardingInvite; import eu.siacs.conversations.utils.ThemeHelper; import static android.support.v7.widget.helper.ItemTouchHelper.LEFT; @@ -300,6 +306,8 @@ public class ConversationsOverviewFragment extends XmppFragment { public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { menuInflater.inflate(R.menu.fragment_conversations_overview, menu); AccountUtils.showHideMenuItems(menu); + final MenuItem easyOnboardInvite = menu.findItem(R.id.action_easy_invite); + easyOnboardInvite.setVisible(EasyOnboardingInvite.anyHasSupport(activity == null ? null : activity.xmppConnectionService)); } @Override @@ -354,10 +362,33 @@ public class ConversationsOverviewFragment extends XmppFragment { case R.id.action_search: startActivity(new Intent(getActivity(), SearchActivity.class)); return true; + case R.id.action_easy_invite: + selectAccountToStartEasyInvite(); + return true; } return super.onOptionsItemSelected(item); } + private void selectAccountToStartEasyInvite() { + final List<Account> accounts = EasyOnboardingInvite.getSupportingAccounts(activity.xmppConnectionService); + if (accounts.size() == 1) { + openEasyInviteScreen(accounts.get(0)); + } else { + final AtomicReference<Account> selectedAccount = new AtomicReference<>(accounts.get(0)); + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity); + alertDialogBuilder.setTitle(R.string.choose_account); + final String[] asStrings = Collections2.transform(accounts, a -> a.getJid().asBareJid().toEscapedString()).toArray(new String[0]); + alertDialogBuilder.setSingleChoiceItems(asStrings, 0, (dialog, which) -> selectedAccount.set(accounts.get(which))); + alertDialogBuilder.setNegativeButton(R.string.cancel, null); + alertDialogBuilder.setPositiveButton(R.string.ok, (dialog, which) -> openEasyInviteScreen(selectedAccount.get())); + alertDialogBuilder.create().show(); + } + } + + private void openEasyInviteScreen(final Account account) { + EasyOnboardingInviteActivity.launch(account, activity); + } + @Override void refresh() { if (this.binding == null || this.activity == null) { diff --git a/src/main/java/eu/siacs/conversations/utils/EasyOnboardingInvite.java b/src/main/java/eu/siacs/conversations/utils/EasyOnboardingInvite.java new file mode 100644 index 000000000..954e2c65a --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/EasyOnboardingInvite.java @@ -0,0 +1,94 @@ +package eu.siacs.conversations.utils; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.common.collect.ImmutableList; + +import java.util.Collections; +import java.util.List; + +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.QuickConversationsService; +import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.xmpp.XmppConnection; + +public class EasyOnboardingInvite implements Parcelable { + + private String domain; + private String uri; + private String landingUrl; + + protected EasyOnboardingInvite(Parcel in) { + domain = in.readString(); + uri = in.readString(); + landingUrl = in.readString(); + } + + public EasyOnboardingInvite(String domain, String uri, String landingUrl) { + this.domain = domain; + this.uri = uri; + this.landingUrl = landingUrl; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(domain); + dest.writeString(uri); + dest.writeString(landingUrl); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<EasyOnboardingInvite> CREATOR = new Creator<EasyOnboardingInvite>() { + @Override + public EasyOnboardingInvite createFromParcel(Parcel in) { + return new EasyOnboardingInvite(in); + } + + @Override + public EasyOnboardingInvite[] newArray(int size) { + return new EasyOnboardingInvite[size]; + } + }; + + public static boolean anyHasSupport(final XmppConnectionService service) { + if (QuickConversationsService.isQuicksy()) { + return false; + } + return getSupportingAccounts(service).size() > 0; + + } + + public static List<Account> getSupportingAccounts(final XmppConnectionService service) { + final ImmutableList.Builder<Account> supportingAccountsBuilder = new ImmutableList.Builder<>(); + final List<Account> accounts = service == null ? Collections.emptyList() : service.getAccounts(); + for(Account account : accounts) { + final XmppConnection xmppConnection = account.getXmppConnection(); + if (xmppConnection != null && xmppConnection.getFeatures().easyOnboardingInvites()) { + supportingAccountsBuilder.add(account); + } + } + return supportingAccountsBuilder.build(); + } + + public String getUri() { + return uri; + } + + public String getLandingUrl() { + return landingUrl; + } + + public String getDomain() { + return domain; + } + + public interface OnInviteRequested { + void inviteRequested(EasyOnboardingInvite invite); + void inviteRequestFailed(String message); + } +} diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 31b3420dd..b65076016 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -52,4 +52,5 @@ public final class Namespace { public static final String BOOKMARKS2_COMPAT = BOOKMARKS2 + "#compat"; public static final String INVITE = "urn:xmpp:invite"; public static final String PARS = "urn:xmpp:pars:0"; + public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite"; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 9929a9c81..3f699e64e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -137,6 +137,7 @@ public class XmppConnection implements Runnable { protected final Account account; private final Features features = new Features(this); private final HashMap<Jid, ServiceDiscoveryResult> disco = new HashMap<>(); + private final HashMap<String, Jid> commands = new HashMap<>(); private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>(); private final Hashtable<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks = new Hashtable<>(); private final Set<OnAdvancedStreamFeaturesLoaded> advancedStreamFeaturesLoadedListeners = new HashSet<>(); @@ -228,6 +229,12 @@ public class XmppConnection implements Runnable { } } + public Jid getJidForCommand(final String node) { + synchronized (this.commands) { + return this.commands.get(node); + } + } + public void prepareNewConnection() { this.lastConnect = SystemClock.elapsedRealtime(); this.lastPingSent = SystemClock.elapsedRealtime(); @@ -1028,6 +1035,9 @@ public class XmppConnection implements Runnable { synchronized (this.disco) { disco.clear(); } + synchronized (this.commands) { + this.commands.clear(); + } } private void sendBindRequest() { @@ -1250,6 +1260,35 @@ public class XmppConnection implements Runnable { }); } + private void discoverCommands() { + final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.setTo(account.getDomain()); + request.addChild("query", Namespace.DISCO_ITEMS).setAttribute("node", Namespace.COMMANDS); + sendIqPacket(request, (account, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + final Element query = response.findChild("query",Namespace.DISCO_ITEMS); + if (query == null) { + return; + } + final HashMap<String, Jid> commands = new HashMap<>(); + for(final Element child : query.getChildren()) { + if ("item".equals(child.getName())) { + final String node = child.getAttribute("node"); + final Jid jid = child.getAttributeAsJid("jid"); + if (node != null && jid != null) { + commands.put(node, jid); + } + } + } + Log.d(Config.LOGTAG,commands.toString()); + synchronized (this.commands) { + this.commands.clear(); + this.commands.putAll(commands); + } + } + }); + } + public boolean isMamPreferenceAlways() { return isMamPreferenceAlways; } @@ -1273,6 +1312,9 @@ public class XmppConnection implements Runnable { if (getFeatures().carbons() && !features.carbonsEnabled) { sendEnableCarbons(); } + if (getFeatures().commands()) { + discoverCommands(); + } } private void sendServiceDiscoveryItems(final Jid server) { @@ -1788,6 +1830,16 @@ public class XmppConnection implements Runnable { return hasDiscoFeature(account.getDomain(), "urn:xmpp:carbons:2"); } + public boolean commands() { + return hasDiscoFeature(account.getDomain(), Namespace.COMMANDS); + } + + public boolean easyOnboardingInvites() { + synchronized (commands) { + return commands.containsKey(Namespace.EASY_ONBOARDING_INVITE); + } + } + public boolean bookmarksConversion() { return hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS_CONVERSION) && pepPublishOptions(); } diff --git a/src/main/res/menu/fragment_conversations_overview.xml b/src/main/res/menu/fragment_conversations_overview.xml index 52937ebd1..38c83df8f 100644 --- a/src/main/res/menu/fragment_conversations_overview.xml +++ b/src/main/res/menu/fragment_conversations_overview.xml @@ -36,6 +36,10 @@ android:visible="@bool/show_individual_search_options" app:showAsAction="never" /> <item + android:id="@+id/action_easy_invite" + android:orderInCategory="89" + android:title="@string/invite_to_app" /> + <item android:id="@+id/action_accounts" android:orderInCategory="90" android:title="@string/action_accounts" diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index f40317ea8..a9670533f 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -950,4 +950,7 @@ <string name="failed_deliveries">Failed deliveries</string> <string name="more_options">More options</string> <string name="no_application_found">No application found</string> + <string name="invite_to_app">Invite to Conversations</string> + <string name="unable_to_parse_invite">Unable to parse invite</string> + <string name="server_does_not_support_easy_onboarding_invites">Server does not support generating invites</string> </resources> |