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