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