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