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