ChannelDiscoveryActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.content.Context;
  4import android.content.Intent;
  5import android.content.SharedPreferences;
  6import android.net.Uri;
  7import android.os.Bundle;
  8import android.preference.PreferenceManager;
  9import android.text.Html;
 10import android.text.method.LinkMovementMethod;
 11import android.view.KeyEvent;
 12import android.view.Menu;
 13import android.view.MenuItem;
 14import android.view.View;
 15import android.view.inputmethod.InputMethodManager;
 16import android.widget.EditText;
 17import android.widget.TextView;
 18import android.widget.Toast;
 19import androidx.annotation.NonNull;
 20import androidx.core.content.ContextCompat;
 21import androidx.databinding.DataBindingUtil;
 22import com.google.android.material.color.MaterialColors;
 23import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 24import com.google.common.base.Strings;
 25
 26import java.util.Collections;
 27import java.util.HashMap;
 28import java.util.List;
 29import java.util.concurrent.atomic.AtomicReference;
 30
 31import eu.siacs.conversations.Config;
 32import eu.siacs.conversations.R;
 33import eu.siacs.conversations.databinding.ActivityChannelDiscoveryBinding;
 34import eu.siacs.conversations.entities.Account;
 35import eu.siacs.conversations.entities.Conversation;
 36import eu.siacs.conversations.entities.Room;
 37import eu.siacs.conversations.services.ChannelDiscoveryService;
 38import eu.siacs.conversations.services.QuickConversationsService;
 39import eu.siacs.conversations.ui.adapter.ChannelSearchResultAdapter;
 40import eu.siacs.conversations.ui.util.PendingItem;
 41import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
 42import eu.siacs.conversations.utils.AccountUtils;
 43import eu.siacs.conversations.xmpp.Jid;
 44import eu.siacs.conversations.xmpp.XmppConnection;
 45import eu.siacs.conversations.xmpp.manager.BookmarkManager;
 46import java.util.Collections;
 47import java.util.List;
 48import java.util.concurrent.atomic.AtomicReference;
 49
 50public class ChannelDiscoveryActivity extends XmppActivity
 51        implements MenuItem.OnActionExpandListener,
 52                TextView.OnEditorActionListener,
 53                ChannelDiscoveryService.OnChannelSearchResultsFound,
 54                ChannelSearchResultAdapter.OnChannelSearchResultSelected {
 55
 56    private static final String CHANNEL_DISCOVERY_OPT_IN = "channel_discovery_opt_in";
 57
 58    private final ChannelSearchResultAdapter adapter = new ChannelSearchResultAdapter();
 59    private final PendingItem<String> mInitialSearchValue = new PendingItem<>();
 60    private ActivityChannelDiscoveryBinding binding;
 61    private MenuItem mMenuSearchView;
 62    private EditText mSearchEditText;
 63
 64    private String[] pendingServices = null;
 65    private ChannelDiscoveryService.Method method = ChannelDiscoveryService.Method.LOCAL_SERVER;
 66    private HashMap<Jid, XmppConnection> mucServices = null;
 67
 68    private boolean optedIn = false;
 69
 70    @Override
 71    protected void refreshUiReal() {}
 72
 73    @Override
 74    protected void onBackendConnected() {
 75        if (pendingServices != null) {
 76            mucServices = new HashMap<>();
 77            for (int i = 0; i < pendingServices.length; i += 2) {
 78                mucServices.put(Jid.of(pendingServices[i]), xmppConnectionService.findAccountByJid(Jid.of(pendingServices[i+1])).getXmppConnection());
 79            }
 80        }
 81
 82        this.method = getMethod(this);
 83
 84        if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) {
 85            final String query;
 86            if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
 87                query = mSearchEditText.getText().toString();
 88            } else {
 89                query = mInitialSearchValue.peek();
 90            }
 91            toggleLoadingScreen();
 92            xmppConnectionService.discoverChannels(query, this.method, this.mucServices, this);
 93        }
 94    }
 95
 96    @Override
 97    protected void onCreate(final Bundle savedInstanceState) {
 98        super.onCreate(savedInstanceState);
 99        binding = DataBindingUtil.setContentView(this, R.layout.activity_channel_discovery);
100        setSupportActionBar(binding.toolbar);
101        Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
102        configureActionBar(getSupportActionBar(), true);
103        binding.list.setAdapter(this.adapter);
104        this.adapter.setOnChannelSearchResultSelectedListener(this);
105        this.optedIn = getPreferences().getBoolean(CHANNEL_DISCOVERY_OPT_IN, false);
106
107        final String search =
108                savedInstanceState == null ? null : savedInstanceState.getString("search");
109        if (search != null) {
110            mInitialSearchValue.push(search);
111        }
112
113        pendingServices = getIntent().getStringArrayExtra("services");
114    }
115
116    private ChannelDiscoveryService.Method getMethod(final Context c) {
117        if (this.mucServices != null) return ChannelDiscoveryService.Method.LOCAL_SERVER;
118        if (Strings.isNullOrEmpty(Config.CHANNEL_DISCOVERY)) {
119            return ChannelDiscoveryService.Method.LOCAL_SERVER;
120        }
121        if (QuickConversationsService.isQuicksy()) {
122            return ChannelDiscoveryService.Method.JABBER_NETWORK;
123        }
124        final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(c);
125        final String m =
126                p.getString(
127                        "channel_discovery_method",
128                        c.getString(R.string.default_channel_discovery));
129        try {
130            return ChannelDiscoveryService.Method.valueOf(m);
131        } catch (IllegalArgumentException e) {
132            return ChannelDiscoveryService.Method.JABBER_NETWORK;
133        }
134    }
135
136    @Override
137    public boolean onCreateOptionsMenu(final Menu menu) {
138        getMenuInflater().inflate(R.menu.channel_discovery_activity, menu);
139        AccountUtils.showHideMenuItems(menu);
140        mMenuSearchView = menu.findItem(R.id.action_search);
141        final View mSearchView = mMenuSearchView.getActionView();
142        mSearchEditText = mSearchView.findViewById(R.id.search_field);
143        mSearchEditText.setHint(R.string.search_channels);
144        final String initialSearchValue = mInitialSearchValue.pop();
145        if (initialSearchValue != null) {
146            mMenuSearchView.expandActionView();
147            mSearchEditText.append(initialSearchValue);
148            mSearchEditText.requestFocus();
149            if ((optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER)
150                    && xmppConnectionService != null) {
151                xmppConnectionService.discoverChannels(initialSearchValue, this.method, this.mucServices, this);
152            }
153        }
154        mSearchEditText.setOnEditorActionListener(this);
155        mMenuSearchView.setOnActionExpandListener(this);
156        return true;
157    }
158
159    @Override
160    public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
161        mSearchEditText.post(
162                () -> {
163                    mSearchEditText.requestFocus();
164                    final InputMethodManager imm =
165                            (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
166                    imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
167                });
168        return true;
169    }
170
171    @Override
172    public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
173        final InputMethodManager imm =
174                (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
175        imm.hideSoftInputFromWindow(
176                mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
177        mSearchEditText.setText("");
178        toggleLoadingScreen();
179        if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) {
180            xmppConnectionService.discoverChannels(null, this.method, this.mucServices, this);
181        }
182        return true;
183    }
184
185    private void toggleLoadingScreen() {
186        adapter.submitList(Collections.emptyList());
187        binding.progressBar.setVisibility(View.VISIBLE);
188        binding.list.setBackgroundColor(
189                MaterialColors.getColor(
190                        binding.list, com.google.android.material.R.attr.colorSurface));
191    }
192
193    @Override
194    public void onStart() {
195        super.onStart();
196        this.method = getMethod(this);
197        if (pendingServices == null && !optedIn && method == ChannelDiscoveryService.Method.JABBER_NETWORK) {
198            final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
199            builder.setTitle(R.string.channel_discovery_opt_in_title);
200            builder.setMessage(Html.fromHtml(getString(R.string.channel_discover_opt_in_message)));
201            builder.setNegativeButton(R.string.cancel, (dialog, which) -> finish());
202            builder.setPositiveButton(R.string.confirm, (dialog, which) -> optIn());
203            builder.setOnCancelListener(dialog -> finish());
204            final androidx.appcompat.app.AlertDialog dialog = builder.create();
205            dialog.setOnShowListener(
206                    d -> {
207                        final TextView textView = dialog.findViewById(android.R.id.message);
208                        if (textView == null) {
209                            return;
210                        }
211                        textView.setMovementMethod(LinkMovementMethod.getInstance());
212                    });
213            dialog.setCanceledOnTouchOutside(false);
214            dialog.show();
215            holdLoading();
216        }
217    }
218
219    private void holdLoading() {
220        adapter.submitList(Collections.emptyList());
221        binding.progressBar.setVisibility(View.GONE);
222        binding.list.setBackgroundColor(
223                MaterialColors.getColor(
224                        binding.list, com.google.android.material.R.attr.colorSurface));
225    }
226
227    @Override
228    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
229        if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
230            savedInstanceState.putString(
231                    "search",
232                    mSearchEditText != null ? mSearchEditText.getText().toString() : null);
233        }
234        super.onSaveInstanceState(savedInstanceState);
235    }
236
237    private void optIn() {
238        SharedPreferences preferences = getPreferences();
239        preferences.edit().putBoolean(CHANNEL_DISCOVERY_OPT_IN, true).apply();
240        optedIn = true;
241        toggleLoadingScreen();
242        xmppConnectionService.discoverChannels(null, this.method, this.mucServices, this);
243    }
244
245    @Override
246    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
247        if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) {
248            toggleLoadingScreen();
249            SoftKeyboardUtils.hideSoftKeyboard(this);
250            xmppConnectionService.discoverChannels(v.getText().toString(), this.method, this.mucServices, this);
251        }
252        return true;
253    }
254
255    @Override
256    public void onChannelSearchResultsFound(final List<Room> results) {
257        runOnUiThread(
258                () -> {
259                    adapter.submitList(results);
260                    binding.progressBar.setVisibility(View.GONE);
261                    if (results.isEmpty()) {
262                        binding.list.setBackground(
263                                ContextCompat.getDrawable(this, R.drawable.background_no_results));
264                    } else {
265                        binding.list.setBackgroundColor(
266                                MaterialColors.getColor(
267                                        binding.list,
268                                        com.google.android.material.R.attr.colorSurface));
269                    }
270                });
271    }
272
273    @Override
274    public void onChannelSearchResult(final Room result) {
275        final List<String> accounts = AccountUtils.getEnabledAccounts(xmppConnectionService);
276        if (accounts.size() == 1) {
277            joinChannelSearchResult(accounts.get(0), result);
278        } else if (accounts.isEmpty()) {
279            Toast.makeText(this, R.string.please_enable_an_account, Toast.LENGTH_LONG).show();
280        } else {
281            final AtomicReference<String> account = new AtomicReference<>(accounts.get(0));
282            final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
283            builder.setTitle(R.string.choose_account);
284            builder.setSingleChoiceItems(
285                    accounts.toArray(new CharSequence[0]),
286                    0,
287                    (dialog, which) -> account.set(accounts.get(which)));
288            builder.setPositiveButton(
289                    R.string.join,
290                    (dialog, which) -> joinChannelSearchResult(account.get(), result));
291            builder.setNegativeButton(R.string.cancel, null);
292            builder.create().show();
293        }
294    }
295
296    @Override
297    public boolean onContextItemSelected(@NonNull MenuItem item) {
298        final Room room = adapter.getCurrent();
299        if (room == null) {
300            return false;
301        }
302        final int itemId = item.getItemId();
303        if (itemId == R.id.share_with) {
304            StartConversationActivity.shareAsChannel(this, room.address);
305            return true;
306        } else if (itemId == R.id.open_join_dialog) {
307            final Intent intent = new Intent(this, StartConversationActivity.class);
308            intent.setAction(Intent.ACTION_VIEW);
309            intent.putExtra("force_dialog", true);
310            intent.setData(Uri.parse(String.format("xmpp:%s?join", room.address)));
311            startActivity(intent);
312            return true;
313        } else {
314            return false;
315        }
316    }
317
318    public void joinChannelSearchResult(final String selectedAccount, final Room result) {
319        final Jid jid = Jid.of(selectedAccount);
320        final Account account = xmppConnectionService.findAccountByJid(jid);
321        final Conversation conversation =
322                xmppConnectionService.findOrCreateConversation(
323                        account, result.getRoom(), true, true, true);
324        account.getXmppConnection()
325                .getManager(BookmarkManager.class)
326                .ensureBookmarkIsAutoJoin(conversation);
327        switchToConversation(conversation);
328    }
329}